Hardening Linux Server
At a glance#
- Purpose: Baseline security hardening checklist for a RHEL-family Linux server.
- Applies to: RHEL, CentOS, Rocky and AlmaLinux 7+. For Ubuntu, see the related article below.
- Risk: Medium — several steps can lock you out if applied carelessly.
- Time: 1–2 hours for a full pass.
Overview#
This is a baseline checklist to apply to a RHEL-family server before it carries production traffic. It is deliberately ordered so that the changes most likely to lock you out come after the ones that will not.
Ubuntu servers have their own, more detailed article: Ubuntu Server Hardening Documentation. Use that one for Ubuntu hosts rather than translating this.
Warning: Keep a second SSH session open throughout, and have console access available. Several steps below — firewall rules and SSH changes in particular — can lock you out of a remote machine. Never close your only session until you have proved the new configuration works in a new one.
Before you start#
- Root or
sudoaccess. - Console access (vCenter, iDRAC/iLO) as a fallback.
- Agreement on a maintenance window if the host is live.
- A record of what the server is for, so you know which services are legitimately needed.
Procedure#
1. Patch the system#
The single highest-value hardening step. Most breaches exploit known, patched vulnerabilities.
sudo yum update -y # RHEL 7
sudo dnf update -y # RHEL 8/9Reboot if the kernel was updated:
sudo needs-restarting -r || sudo rebootEstablish a recurring patch schedule. A one-off update is not hardening.
2. Remove unnecessary packages and disable unused services#
Every listening service is attack surface. List what is actually running:
sudo systemctl list-units --type=service --state=running
sudo ss -tulpnFor each listening port, decide whether it is needed. Disable what is not:
sudo systemctl disable --now <service>Common candidates on a server build: cups, avahi-daemon, bluetooth, rpcbind, postfix (unless the host actually sends mail).
3. Enforce a password policy#
sudo nano /etc/security/pwquality.confminlen = 14
dcredit = -1
ucredit = -1
ocredit = -1
lcredit = -1Set password ageing defaults in /etc/login.defs:
PASS_MAX_DAYS 90
PASS_MIN_DAYS 1
PASS_WARN_AGE 144. Restrict administrative access#
Grant sudo only to accounts that need it, and prefer group membership over individual entries:
sudo usermod -aG wheel usernameReview who currently holds it:
grep -Po '^sudo.+:\K.*$' /etc/group
lid -g wheelRemove or lock any account that is no longer needed:
sudo usermod -L olduser5. Harden SSH#
Edit the daemon configuration:
sudo nano /etc/ssh/sshd_configPermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
Protocol 2Warning: Before setting PasswordAuthentication no, confirm your SSH key works. Log in with the key from a new session first. Getting this wrong on a remote server means a trip to the console.
Validate the syntax before restarting — this catches typos that would otherwise stop sshd from starting at all:
sudo sshd -t
sudo systemctl restart sshd6. Configure the firewall#
Default-deny, then allow only what is required:
sudo systemctl enable --now firewalld
sudo firewall-cmd --set-default-zone=drop
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reloadReview the result:
sudo firewall-cmd --list-allWarning: Adding SSH to the permanent rules before reloading is essential. Reloading with a drop-all zone and no SSH rule ends the session immediately.
7. Protect the boot loader#
Prevents the single-user-mode password reset described in Changing root password Redhat.
sudo grub2-setpassword
sudo grub2-mkconfig -o /boot/grub2/grub.cfgRecord the password in the team password manager. Losing it makes recovery considerably harder.
8. Tune kernel network parameters#
sudo nano /etc/sysctl.d/99-hardening.conf# Ignore ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Do not accept source-routed packets
net.ipv4.conf.all.accept_source_route = 0
# Do not accept ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# Log packets with impossible addresses
net.ipv4.conf.all.log_martians = 1
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
# Disable IPv6 if genuinely unused on this network
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1Apply:
sudo sysctl --systemNote: Only disable IPv6 if you are certain nothing on the host depends on it. Some applications bind to ::1 and will fail to start without it.
9. Enable and configure auditing#
The audit daemon records who touched what, which is what turns an incident into an investigable one.
sudo systemctl enable --now auditdAdd persistent watches on files that matter. Put them in a rules file rather than using auditctl directly, so they survive reboot:
sudo nano /etc/audit/rules.d/hardening.rules-w /etc/passwd -p wa -k passwd-changes
-w /etc/shadow -p wa -k shadow-changes
-w /etc/sudoers -p wa -k sudoers-changes
-w /var/www/html -p wa -k web-contentLoad them:
sudo augenrules --load
sudo auditctl -lQuery the audit log by key:
sudo ausearch -k passwd-changes -i
sudo ausearch -ts today -k sudoers-changes -i
sudo aureport --summaryNote: The original version of this article used auditctl -w directly. Those rules are lost on reboot. Use the rules file above instead.
10. Keep SELinux enforcing#
getenforceIf it returns anything other than Enforcing, fix it in /etc/selinux/config and reboot. Disabling SELinux to make an application work is a shortcut that costs you a major security control — write a policy exception instead.
11. Verify logging and backups#
Confirm logs are being written and shipped somewhere off-host, so they survive a compromise of this machine:
sudo systemctl status rsyslog
sudo journalctl --disk-usageConfirm the host is covered by the backup schedule, and that a restore has actually been tested.
Verification#
Work through this list after applying the changes:
# SSH config valid and root login refused
sudo sshd -t
ssh root@localhost # should be rejected
# Firewall active and minimal
sudo firewall-cmd --list-all
# SELinux enforcing
getenforce
# Audit rules loaded
sudo auditctl -l
# Nothing unexpected listening
sudo ss -tulpn
# Kernel parameters applied
sudo sysctl net.ipv4.tcp_syncookiesMost importantly, open a completely new SSH session and confirm you can still log in and escalate to root before closing your existing one.
Troubleshooting#
| Symptom | Cause and fix |
|---|---|
| Locked out after firewall change | Connect via console and run sudo firewall-cmd --add-service=ssh --permanent && sudo firewall-cmd --reload. |
sshd will not start | A syntax error in sshd_config. Run sudo sshd -t to see the offending line. |
| Application breaks after hardening | Usually SELinux or the firewall. Check sudo ausearch -m avc -ts recent for SELinux denials before disabling anything. |
| Audit rules gone after reboot | They were added with auditctl rather than a rules file. Move them to /etc/audit/rules.d/. |
| Cannot edit GRUB at boot | Expected — that is what step 7 does. Use the GRUB password from the password manager. |
Related#
- Ubuntu Server Hardening Documentation — the fuller Ubuntu-specific procedure.
- Changing root password Redhat
- cPanel Server Crypto Mining Compromise — what an unhardened server looks like after the fact.