Production servers are targets. Every unpatched service, every default configuration, every weak password is a potential entry point. I've seen compromised boxes that ran for weeks because nobody checked the basics.
This checklist walks through twelve hardening steps that actually matter. Not theoretical best practices—concrete changes that stop real attacks.
1. Lock down SSH access
SSH is your first line of defense and your biggest risk. Default configurations allow password authentication, permit root login, and listen on a well-known port.
Disable root login and password authentication in /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Port 2222
Change the port if you want to cut down on automated scans, but don't rely on it for security. Use SSH keys exclusively. If you must allow passwords for specific users, restrict them with Match User directives.
Restart SSH carefully:
sudo sshd -t # test config first
sudo systemctl restart sshd
Keep your current session open until you've verified the new config works. I've locked myself out more than once.
2. Configure a firewall with minimal rules
Only open ports you actually use. Start with everything blocked and whitelist services.
For ufw on Ubuntu/Debian:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2222/tcp # your SSH port
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
On RHEL/CentOS with firewalld:
sudo firewall-cmd --set-default-zone=drop
sudo firewall-cmd --zone=public --add-port=2222/tcp --permanent
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --reload
Check your rules match your services. A web server needs 80 and 443. A database server probably doesn't need any inbound ports if applications connect locally.
3. Install and configure fail2ban
Fail2ban watches logs and bans IPs after repeated failed attempts. It's simple and effective against brute-force attacks.
Install it:
sudo apt install fail2ban # Debian/Ubuntu
sudo yum install fail2ban # RHEL/CentOS
Create /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
Restart and verify:
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
Add jails for any service that authenticates: FTP, mail servers, web applications with login forms. In support tickets I handled, the usual culprit was a default config that didn't match the actual SSH port.
4. Apply least-privilege user permissions
Run services as dedicated users, not root. Each application gets its own account with minimal permissions.
Create a service user:
sudo useradd -r -s /bin/false -d /var/lib/myapp myapp
The -r flag creates a system account. The shell is set to /bin/false so direct login is impossible. Set ownership on application directories:
sudo chown -R myapp:myapp /var/lib/myapp
sudo chmod 750 /var/lib/myapp
Never run your web server, database, or custom applications as root. If the process is compromised, the attacker inherits its permissions.
5. Harden kernel parameters with sysctl
The kernel exposes hundreds of tunables. A few matter for security.
Edit /etc/sysctl.conf or create /etc/sysctl.d/99-hardening.conf:
# IP forwarding off unless you're a router
net.ipv4.ip_forward = 0
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Ignore source-routed packets
net.ipv4.conf.all.accept_source_route = 0
# Enable TCP SYN cookie protection
net.ipv4.tcp_syncookies = 1
# Log martian packets
net.ipv4.conf.all.log_martians = 1
# Restrict dmesg access to root
kernel.dmesg_restrict = 1
# Restrict kernel pointer exposure
kernel.kptr_restrict = 2
Apply changes:
sudo sysctl -p
These settings close common network-level attacks and information leaks. They won't break normal operation.
6. Enable SELinux or AppArmor
Mandatory access control adds another layer. Even if an attacker compromises a process, MAC policies limit what that process can do.
On RHEL/CentOS, check SELinux status:
sudo getenforce
If it's disabled or permissive, edit /etc/selinux/config:
SELINUX=enforcing
SELINUXTYPE=targeted
Reboot. Watch logs for denials:
sudo ausearch -m avc -ts recent
On Ubuntu, AppArmor is usually active. Check with:
sudo aa-status
Both systems take effort to configure correctly, but they catch privilege escalations that file permissions miss. Start with enforcing mode on new systems; retrofitting is harder.
7. Set up comprehensive audit logging
The auditd daemon tracks system calls, file access, and user actions. When something goes wrong, you need those logs.
Install auditd:
sudo apt install auditd # Debian/Ubuntu
sudo yum install audit # RHEL/CentOS
Add rules in /etc/audit/rules.d/hardening.rules:
# Watch for changes to user and group files
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
# Monitor sudo usage
-w /etc/sudoers -p wa -k sudoers
-w /var/log/sudo.log -p wa -k sudoers
# Track network configuration changes
-w /etc/sysconfig/network -p wa -k network
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k network
# Monitor file deletion by users
-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -k delete
Load rules and restart:
sudo augenrules --load
sudo systemctl restart auditd
Search logs with ausearch or review /var/log/audit/audit.log directly. Rotate logs to prevent disk exhaustion.
8. Keep packages updated automatically
Unpatched software is the easiest way in. Automate updates for security patches.
On Debian/Ubuntu, install unattended-upgrades:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Configure /etc/apt/apt.conf.d/50unattended-upgrades to apply security updates only:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
On RHEL/CentOS, enable automatic updates with dnf-automatic:
sudo yum install dnf-automatic
sudo systemctl enable --now dnf-automatic.timer
Edit /etc/dnf/automatic.conf and set apply_updates = yes under the [commands] section.
Test your application after kernel or library updates. Most patches are safe, but a broken update is better than a compromised server.
9. Remove unnecessary services and packages
Every running service is attack surface. If you don't need it, stop it.
List active services:
sudo systemctl list-units --type=service --state=running
Disable services you don't use:
sudo systemctl disable --now bluetooth.service
sudo systemctl disable --now cups.service
Remove unneeded packages:
sudo apt autoremove # Debian/Ubuntu
sudo yum autoremove # RHEL/CentOS
Common candidates: mail transfer agents if you don't send mail, FTP servers if you use SFTP, GUI packages on a headless server. Check your server's purpose and strip everything else.
10. Implement file integrity monitoring
File integrity monitoring alerts you when critical files change unexpectedly. AIDE is the standard tool.
Install AIDE:
sudo apt install aide # Debian/Ubuntu
sudo yum install aide # RHEL/CentOS
Initialize the database:
sudo aideinit
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Run checks manually:
sudo aide --check
Schedule daily checks with cron:
echo '0 5 * * * root /usr/bin/aide --check | mail -s "AIDE Report" [email protected]' | sudo tee /etc/cron.d/aide
AIDE watches files listed in /etc/aide/aide.conf. Adjust the config to focus on system binaries, config files, and application directories.
11. Restrict and monitor root access
Root access should be rare and logged. Use sudo for administrative tasks and restrict who can use it.
Edit /etc/sudoers with visudo:
# Allow specific users to run specific commands
admin ALL=(ALL) ALL
webuser ALL=(www-data) NOPASSWD: /usr/sbin/service nginx reload
Require a password for sudo unless there's a strong reason not to. Log all sudo commands:
Defaults logfile=/var/log/sudo.log
Defaults log_year, log_host, loglinelen=0
Disable direct root login entirely by locking the root password:
sudo passwd -l root
You can still escalate with sudo -i. This prevents root login over SSH even if PermitRootLogin is misconfigured.
12. Deploy intrusion detection with OSSEC or similar
Host-based intrusion detection systems analyze logs, monitor files, and detect rootkits. OSSEC is open-source and widely used.
Install OSSEC:
wget https://github.com/ossec/ossec-hids/archive/refs/tags/3.7.0.tar.gz
tar -xzf 3.7.0.tar.gz
cd ossec-hids-3.7.0
sudo ./install.sh
Choose "local" installation for a standalone server. OSSEC monitors logs in real time, checks file integrity, detects rootkits, and can trigger active responses like blocking IPs.
Configure alerts in /var/ossec/etc/ossec.conf:
<email_notification>yes</email_notification>
<email_to>[email protected]</email_to>
<smtp_server>localhost</smtp_server>
<email_from>[email protected]</email_from>
Start the service:
sudo /var/ossec/bin/ossec-control start
Review alerts in /var/ossec/logs/alerts/alerts.log. Tune rules to reduce noise—out of the box, OSSEC can be chatty.
Alternatives include Wazuh (OSSEC fork with better UI) and Tripwire for file integrity. Pick one and keep it updated.
How often should you review hardening?
Re-run this checklist after major software updates, role changes, or security incidents. An annual review catches configuration drift.
How do you verify hardening worked?
Run vulnerability scanners like Lynis or OpenVAS against your server. Check exposed ports with nmap from an external IP. Review logs for authentication failures and unexpected connections.
What if hardening breaks an application?
Rollback one change at a time. Check SELinux or AppArmor denials first—they're the usual suspect. Firewall rules and kernel parameters rarely cause application breakage if configured correctly.
Do you need all twelve steps?
Prioritize based on your threat model. Steps 1-4 (SSH, firewall, fail2ban, user permissions) are non-negotiable. Steps 6 and 12 (MAC and IDS) add significant overhead but catch advanced threats.
How do you harden a server that's already in production?
Carefully. Test changes in a staging environment first. Apply hardening during maintenance windows. Keep backups and a rollback plan ready.
Start with SSH and firewall rules
If you only do two things, lock down SSH and configure the firewall. Those two steps stop most automated attacks. Then work through the rest of the list as time permits.
Hardening isn't a one-time task. New vulnerabilities emerge, configurations drift, and services change. Schedule regular reviews and treat this checklist as a baseline, not a finish line.
