Enterprise Linux Server Administration, Hardening & Migration Guide
A comprehensive, production-ready handbook for Enterprise Linux (RHEL, Rocky Linux, AlmaLinux) systems administration. This guide covers service migration, database operations, SSH security, certbot…
Security Best Practice: Never store plaintext production passwords, KeePassXC master keys, or GitLab/Github Personal Access Tokens (PATs) in your public source code. Always use placeholder tokens in repository files and load credentials dynamically from secure vaults, environment variables, or local .env configs.
Before running operations, secure your key management files and personal access tokens:
GitLab PAT (Personal Access Tokens): Ensure your GitLab PATs are stored in a secure credential helper or loaded as environment variables (e.g., export GITLAB_TOKEN="glpat-...").
KeePassXC Password Manager: Secure your master key offline. Do not write credentials in plaintext configuration files.
Run these commands to apply the standard production permissions for web content:
# Standard directories to 755 (or 750 for tighter security)sudo find /path/to/sample-webapps/ -type d -exec chmod 0755 {} \;# Standard files to 644sudo find /path/to/sample-webapps/ -type f -exec chmod 0644 {} \;# Hardened permissions for the configuration filesudo chmod 600 wp-config.php
If SELinux is set to Enforcing (check via sestatus), Apache will be blocked from reading files unless the correct context is applied:
# Set web server read-only file context recursivelysudo chcon -R -t httpd_sys_content_t /var/www/html/sample-webapps/# Apply persistent file contexts so they survive a system restoreconsudo semanage fcontext -a -t httpd_sys_content_t "/var/www/html/sample-webapps(/.*)?"sudo restorecon -R -v /var/www/html/sample-webapps/
To authorize a SSH public key generated via PuTTY (.pub format), convert the key into standard OpenSSH format:
# 1. Paste the Putty-formatted key into a temporary filenano /home/temp/dev1.pub# 2. Convert and append the key to authorized_keysssh-keygen -i -f /home/temp/dev1.pub >> ~/.ssh/authorized_keys# 3. Clean up the temporary filerm /home/temp/dev1.pub
5. Multiple PHP Version Management (via Remi RPM)#
For enterprise web environments running legacy and modern codebases simultaneously:
# Install PHP 8.3 and standard extensions (the legacy line)sudo dnf install -y php83-php-fpm php83-php-mysqlnd php83-php-gd php83-php-mbstring php83-php-xml# Install PHP 8.4 and standard extensions (the current line)sudo dnf install -y php84-php-fpm php84-php-mysqlnd php84-php-gd php84-php-mbstring php84-php-xml
# 1. Log into MariaDB/MySQL CLImysql -u root -p# 2. Setup the database and user permissionsCREATE DATABASE target_db_name;CREATE USER 'db_user'@'localhost' IDENTIFIED BY 'YOUR_STRONG_PASSWORD';GRANT ALL PRIVILEGES ON target_db_name.* TO 'db_user'@'localhost';FLUSH PRIVILEGES;EXIT;# 3. Import the backup SQL filemysql -u root -p target_db_name < /tmp/[database_name]_backup.sql
Ensure Apache can traverse through directories from root to the virtual host's public_html:
# Set search execution permissions on parentschmod 711 /home/sample.comchmod 711 /home/sample.com/wwwchmod 711 /home/sample.com/www/htmlchmod 755 /home/sample.com/www/html/sample-web-apps/public_html# Assign ownership to the web serversudo chown -R apache:apache /home/sample.com/
Certbot's packaged certbot.timer already runs certbot renew twice a day, so there is nothing further to schedule. The --deploy-hook above is the part that matters: certbot stores it in the renewal configuration for that certificate, and runs it only when a renewal actually succeeds — never on an ordinary timer tick. Without it, Apache keeps serving the old certificate until you reload it by hand or reboot.
To free up space on /boot by removing old or unused kernel versions:
# 1. List currently installed kernel-coresrpm -qa | grep kernel-core# 2. Check current active boot optionssudo grubby --info=ALL | grep -E "kernel|index"# 3. Set the default booting kernel versionsudo grubby --set-default /boot/vmlinuz-5.14.0-503.40.1.el9_5.x86_64# 4. Reboot system to run on the new default kernelsudo reboot# 5. Verify the current active kernel versionuname -r# 6. Safe delete the old kernel-core packagessudo dnf remove kernel-core-5.14.0-284.11.1.el9_2.x86_64# 7. List kernels and clean unused utility packagesdnf list | grep kernel# Do not remove policycoreutils-python-utils here — `semanage`, used# throughout this guide, lives in that package.sudo dnf remove python3-setuptools
Add security policies inside your Apache configuration or .htaccess to mitigate clickjacking and injection threats:
Header set Strict-Transport-Security "max-age=31536000; includeSubDomains"Header set X-Content-Type-Options "nosniff"Header set X-Frame-Options "sameorigin"Header set Referrer-Policy "strict-origin-when-cross-origin"Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"Header set Content-Security-Policy "default-src * data: blob:; script-src https: blob: 'unsafe-inline' 'unsafe-eval'; style-src https: 'unsafe-inline'; frame-src https: blob: self"
Warning
The CSP above is a permissive starting point, not a finished policy — default-src * plus 'unsafe-inline' and 'unsafe-eval' is what a legacy WordPress stack typically needs to render at all. Treat it as a baseline to tighten, and rebuild it for your own asset origins instead of copying someone else's allow-list. X-XSS-Protection is deliberately absent: modern browsers removed the auditor, and the header can introduce its own issues.