Skip to content
Back to Blog
Security10 min read

Server Hardening Checklist: 12 Steps to Lock Down Linux

A practical twelve-step server hardening checklist covering SSH, firewall, fail2ban, SELinux, audit logging, and intrusion detection to secure your Linux VPS.

Written by Abdul AbrorTechnical Hosting Support Engineer
Server Hardening Checklist: 12 Steps to Lock Down Linux
On this page

A fresh Linux server ships with conveniences that attackers love. Root login over SSH. Password authentication. Services you'll never use listening on ports you've never checked. I've restored too many compromised servers where the initial entry point was something trivial—a default config line nobody changed.

This checklist walks you through twelve hardening steps that close the easy doors. You won't need to be a security specialist to apply them, and you won't break a working server if you follow the order. Each step takes five to thirty minutes depending on your setup.

1. Disable root SSH login and enforce key-based auth

Password authentication over SSH is the most common attack vector I see in support tickets. Bots hammer port 22 with credential lists around the clock.

Edit /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no

Before you restart sshd, verify that: - You have a non-root sudo user. - That user's ~/.ssh/authorized_keys contains your public key. - You can log in as that user with your key in a separate terminal session.

Only then run systemctl restart sshd. Test immediately. If you lock yourself out, most VPS control panels offer a console or rescue mode.

2. Change the default SSH port

This is security through obscurity, but it cuts brute-force noise by ninety percent. Pick a port above 1024 and below 65535 that doesn't conflict with anything else you run.

In /etc/ssh/sshd_config:

Port 2849

Restart sshd, then update your firewall rules (step 3) to allow the new port before you close your current session. Connect on the new port in a second terminal to confirm it works.

3. Configure a firewall with default-deny

Use iptables with iptables-persistent, nftables, or ufw depending on your distribution. I'll show ufw because it's the simplest for this purpose.

apt install ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow 2849/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw enable

Add only the ports your services actually need. A web server needs 80 and 443. A mail server needs 25, 465, 587, 993, and maybe 995. Everything else stays closed.

Check active rules with ufw status numbered. If you use a control panel like cPanel or Plesk, check their documentation—they may manage firewall rules themselves and conflict with manual ufw changes.

4. Install and configure fail2ban

Fail2ban watches logs for repeated failed login attempts and temporarily bans the source IP. It works for SSH, HTTP auth, mail servers, and dozens of other services.

apt install fail2ban
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit /etc/fail2ban/jail.local:

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5

[sshd]
enabled = true
port = 2849

Restart with systemctl restart fail2ban. Check active jails with fail2ban-client status and banned IPs with fail2ban-client status sshd. You'll see bans accumulate within hours if your server is internet-facing.

5. Keep packages updated automatically

Unpatched systems get owned. You need a process that applies security updates without waiting for you to remember.

On Debian and Ubuntu, install unattended-upgrades:

apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic security updates and configure email notifications if something breaks. For RHEL-based systems, enable dnf-automatic instead.

Check /var/log/unattended-upgrades/ periodically to confirm updates are running. A stale log means the service stopped and you didn't notice.

6. Remove or disable unnecessary services

Every running service is a potential attack surface. List what's listening:

ss -tulnp

You'll see everything bound to a port. For each one, ask: do I actually use this? If you don't recognize a service, check its systemd unit with systemctl status <service-name>.

Disable what you don't need:

systemctl disable --now <service-name>

Common candidates: rpcbind, avahi-daemon, cups (unless you're printing from this server, which you aren't). On a typical VPS, you should see SSH, your web server, maybe a database, and not much else.

7. Restrict user permissions and implement least privilege

Don't run applications as root. Create dedicated service accounts with no login shell and no home directory:

useradd -r -s /usr/sbin/nologin -d /nonexistent appuser

Set ownership so the app can read its files but not write to binaries:

chown -R appuser:appuser /var/www/app
chmod -R 750 /var/www/app

For sudo access, use /etc/sudoers.d/ drop-in files instead of editing sudoers directly. Grant only the specific commands each user needs, not blanket ALL=(ALL) ALL.

8. Enable and configure SELinux or AppArmor

Mandatory access control (MAC) systems enforce policies even if file permissions are misconfigured. SELinux is standard on RHEL-based distributions; AppArmor ships with Debian and Ubuntu.

On RHEL/CentOS/AlmaLinux, check the current mode:

getenforce

If it says Disabled, edit /etc/selinux/config and set SELINUX=enforcing, then reboot. Expect some services to break initially—check /var/log/audit/audit.log for denials and write policies or adjust booleans as needed.

On Ubuntu, AppArmor is usually active. Check with:

aa-status

Most distributions ship profiles for common daemons. If you install something custom, you may need to write a profile or set the service to complain mode while you tune it.

This is the step that takes the most iteration. Don't skip it.

9. Configure kernel hardening parameters

Edit /etc/sysctl.conf or create /etc/sysctl.d/99-hardening.conf:

# IP forwarding off unless you're routing
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
net.ipv6.conf.all.accept_source_route = 0

# SYN flood protection
net.ipv4.tcp_syncookies = 1

# Ignore ICMP ping
net.ipv4.icmp_echo_ignore_all = 1

# Log suspicious packets
net.ipv4.conf.all.log_martians = 1

Apply with sysctl -p. Reboot to confirm the settings persist.

10. Enable audit logging with auditd

The audit daemon records security-relevant events: who ran sudo, which files were accessed, failed login attempts, permission changes.

apt install auditd audispd-plugins
systemctl enable --now auditd

Add rules in /etc/audit/rules.d/audit.rules. Track sudo usage:

-w /usr/bin/sudo -p x -k sudo_exec

Watch sensitive files:

-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/ssh/sshd_config -p wa -k sshd_config

Reload rules with augenrules --load. Search logs with ausearch -k <key> or dump everything with aureport.

The logs live in /var/log/audit/audit.log. Rotate them or they'll fill your disk.

11. Set up file integrity monitoring

File integrity monitoring (FIM) detects unauthorized changes to system binaries, configs, and application code. AIDE and Tripwire are the standard tools.

Install AIDE:

apt install aide
aideinit

This creates a baseline database of file hashes. It takes several minutes on a large filesystem. When it finishes, copy the database:

cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run checks with aide --check. Schedule it in cron:

0 3 * * * /usr/bin/aide --check | mail -s "AIDE report" [email protected]

Every time you update packages or change a config intentionally, regenerate the baseline with aideinit and copy the new database. Otherwise you'll get false positives forever.

12. Deploy intrusion detection with OSSEC or Wazuh

Host-based intrusion detection systems (HIDS) correlate logs, detect rootkits, and alert on policy violations. OSSEC is lightweight and easy to deploy. Wazuh is a maintained fork with a better web interface.

Install OSSEC in local mode:

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
./install.sh

Choose "local" installation when prompted. Edit /var/ossec/etc/ossec.conf to enable rootkit detection, log monitoring, and email alerts. Start with:

/var/ossec/bin/ossec-control start

Check alerts in /var/ossec/logs/alerts/alerts.log. You'll get noise at first—tune rules in /var/ossec/rules/local_rules.xml to reduce false positives.

For a multi-server setup, run Wazuh with a manager node and agents on each host. The manager aggregates alerts and provides a web dashboard.

What about cloud-specific hardening?

If you're on AWS, Azure, or GCP, apply these steps first. Then layer on platform-specific controls: security groups instead of relying only on host firewalls, IAM roles with least privilege, instance metadata service hardening (IMDSv2 on AWS), and disk encryption.

The principles don't change. Defense in depth.

Start with SSH and firewall, then layer up

You don't have to implement all twelve steps in one afternoon. Start with SSH hardening and the firewall—those two changes block the majority of automated attacks. Add fail2ban next, then work through the rest over a few weeks.

Document what you enable and why. Six months from now, when a service breaks and you need to troubleshoot, you'll want to know which hardening rule might be the cause. A checklist you actually followed beats a perfect security policy that stayed in a Google Doc.

FAQ

How often should I review hardening configs?

After every major package update or application deployment. Quarterly at minimum. When something breaks, check if a hardening rule blocked it before you disable the rule.

Can I automate this checklist?

Yes. Configuration management tools like Ansible, Puppet, or SaltStack can enforce every item here. Write the playbook once, apply it to every server, and re-run it on a schedule to catch drift.

Will SELinux break my applications?

Probably, at first. Set it to permissive mode, run your app through normal operations, review the audit log, then write or adjust policies. Switch to enforcing only after you've cleared the denials.

Do I need all twelve steps?

Steps 1 through 5 are non-negotiable. The rest depend on your threat model and compliance requirements. A personal blog can skip AIDE and OSSEC. A server handling customer data cannot.