Skip to content
Back to Blog
Security11 min read

Server Hardening Checklist: 12 Steps to Lock Down Linux

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

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

A freshly deployed Linux server is like a house with unlocked windows. The OS vendor ships it ready to run, not ready to resist. I've seen servers compromised within hours of going live because someone skipped the basics—default SSH port open, root login enabled, no firewall.

This checklist walks you through twelve hardening steps that stop most automated attacks and make targeted ones much harder. You won't need exotic tools or a security degree. Just SSH access, root privileges, and thirty minutes.

1. Disable root SSH login and use key authentication

Password authentication over SSH is the easiest vector for brute-force bots. They hammer common usernames and passwords around the clock.

First, create a non-root user with sudo privileges if you haven't already:

adduser deployuser
usermod -aG sudo deployuser

Generate an SSH key pair on your local machine if you don't have one:

ssh-keygen -t ed25519 -C "[email protected]"

Copy the public key to the server:

ssh-copy-id deployuser@your_server_ip

Test the key login in a new terminal before locking yourself out. Once confirmed, edit /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Restart SSH:

systemctl restart sshd

Root can no longer log in remotely. Bots targeting root passwords hit a wall.

2. Change the default SSH port

Port 22 gets scanned constantly. Moving SSH to a non-standard port won't stop a determined attacker, but it drops the noise in your logs by 90%.

In /etc/ssh/sshd_config, change:

Port 2849

Pick any unused port above 1024. Update your firewall rules before restarting SSH or you'll lock yourself out:

ufw allow 2849/tcp
systemctl restart sshd

Next login, specify the port:

ssh -p 2849 deployuser@your_server_ip

3. Configure a firewall with minimal open ports

Default policies should deny everything except what you explicitly need. UFW makes this simple on Ubuntu and Debian:

ufw default deny incoming
ufw default allow outgoing
ufw allow 2849/tcp  # your SSH port
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

For CentOS or RHEL, use firewalld:

firewall-cmd --set-default-zone=drop
firewall-cmd --permanent --add-port=2849/tcp
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

Verify with ufw status or firewall-cmd --list-all. If a port isn't serving a public service, close it.

4. Install and configure fail2ban

Fail2ban watches log files for repeated failed authentication attempts and temporarily bans offending IPs.

Install it:

apt install fail2ban   # Debian/Ubuntu
yum install fail2ban   # CentOS/RHEL

Copy the default config:

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

Edit /etc/fail2ban/jail.local and adjust the SSH jail:

[sshd]
enabled = true
port = 2849
maxretry = 3
bantime = 3600
findtime = 600

Start and enable fail2ban:

systemctl enable fail2ban
systemctl start fail2ban

Check banned IPs:

fail2ban-client status sshd

In support tickets I handled, fail2ban stopped most SSH brute-force campaigns within the first hour.

5. Enforce strong user permissions and disable unused accounts

Every active user account is a potential entry point. Audit your user list:

cat /etc/passwd | grep -v nologin | grep -v false

Lock or remove accounts you don't recognize or don't need:

usermod -L olduser      # lock
userdel -r olduser      # remove with home directory

Set a strong password policy. Edit /etc/login.defs:

PASS_MAX_DAYS 90
PASS_MIN_DAYS 1
PASS_MIN_LEN 12
PASS_WARN_AGE 7

For existing users, enforce expiration:

chage -M 90 deployuser

Check sudo privileges:

cat /etc/sudoers.d/*

Remove sudo from users who don't need it.

6. Harden kernel parameters with sysctl

The kernel exposes tunable parameters that control networking and process behavior. A few tweaks reduce your attack surface.

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

# Disable IP forwarding
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0

# Enable SYN cookies
net.ipv4.tcp_syncookies = 1

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

# Ignore ICMP ping requests
net.ipv4.icmp_echo_ignore_all = 1

Apply changes:

sysctl -p

These settings won't break normal services but make reconnaissance and spoofing attacks harder.

7. Enable SELinux or AppArmor

Mandatory access control systems confine processes to the minimum permissions they need. Even if an attacker exploits a service, the damage stays contained.

On RHEL-based systems, check SELinux status:

getenforce

If it returns Permissive or Disabled, enable it. Edit /etc/selinux/config:

SELINUX=enforcing

Reboot. SELinux in enforcing mode will block unauthorized file access and network binds.

On Ubuntu, AppArmor is usually active by default:

aa-status

If not, install and enable it:

apt install apparmor apparmor-utils
systemctl enable apparmor
systemctl start apparmor

Don't disable SELinux or AppArmor just because an application complains. Fix the policy or the application's behavior instead.

8. Configure centralized audit logging

You can't investigate what you didn't log. The auditd daemon records security-relevant events—file access, system calls, authentication attempts.

Install auditd:

apt install auditd      # Debian/Ubuntu
yum install audit       # CentOS/RHEL

Start and enable it:

systemctl enable auditd
systemctl start auditd

Add rules to watch sensitive files. Edit /etc/audit/rules.d/audit.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/log/auth.log -p wa -k auth_log_changes

Reload rules:

augenrules --load

Search audit logs:

ausearch -k passwd_changes

Ship logs to a remote syslog server if you manage multiple machines. Local logs disappear if the server gets rooted.

9. Keep packages updated and enable automatic security patches

Unpatched software is the low-hanging fruit attackers pick first. Zero-days are rare; exploiting a six-month-old CVE is common.

On Debian/Ubuntu, enable unattended upgrades:

apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades

Edit /etc/apt/apt.conf.d/50unattended-upgrades to allow automatic security updates:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
};

On CentOS/RHEL, use yum-cron:

yum install yum-cron
systemctl enable yum-cron
systemctl start yum-cron

Edit /etc/yum/yum-cron.conf and set:

apply_updates = yes

Manually check for updates weekly:

apt update && apt upgrade        # Debian/Ubuntu
yum update                        # CentOS/RHEL

10. Remove or disable unnecessary services

Every running service is another potential exploit target. If you're not using it, turn it off.

List active services:

systemctl list-units --type=service --state=running

Common services to disable on a web server:

systemctl disable avahi-daemon
systemctl stop avahi-daemon

systemctl disable cups
systemctl stop cups

systemctl disable bluetooth
systemctl stop bluetooth

Remove packages you don't need:

apt remove --purge packagename

Check listening ports:

ss -tuln

If you see a port you don't recognize, track down the process with lsof or netstat and decide if it should run.

11. Set up file integrity monitoring

File integrity monitoring detects unauthorized changes to critical system files. AIDE and Tripwire are the standard tools.

Install AIDE:

apt install aide         # Debian/Ubuntu
yum install aide         # CentOS/RHEL

Initialize the database:

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

Run a check:

aide --check

AIDE compares current file states against the baseline and reports changes. Schedule daily checks with cron:

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

Update the database after legitimate changes:

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

So what if an attacker modifies the AIDE database itself? Store the baseline on read-only media or a separate trusted system.

12. Install an intrusion detection system

Host-based intrusion detection watches system calls, file access, and network activity for suspicious patterns. OSSEC is open-source and works across Linux distributions.

Install OSSEC:

wget https://github.com/ossec/ossec-hids/archive/refs/tags/3.7.0.tar.gz
tar -xvzf 3.7.0.tar.gz
cd ossec-hids-3.7.0
./install.sh

Follow the prompts and choose "local" installation for a standalone server. Start OSSEC:

/var/ossec/bin/ossec-control start

OSSEC monitors logs, checks file integrity, detects rootkits, and alerts on policy violations. Configure email alerts in /var/ossec/etc/ossec.conf:

<global>
  <email_notification>yes</email_notification>
  <email_to>[email protected]</email_to>
  <smtp_server>localhost</smtp_server>
  <email_from>[email protected]</email_from>
</global>

Restart OSSEC after config changes:

/var/ossec/bin/ossec-control restart

Review alerts in /var/ossec/logs/alerts/alerts.log. Tune rules over time to reduce false positives.

What to check first

Start with SSH hardening, firewall rules, and fail2ban. Those three stop the majority of automated attacks within an hour. Add the rest over the next few days.

Hardening isn't a one-time task. New vulnerabilities emerge, services get added, users come and go. Schedule monthly reviews of user accounts, firewall rules, and package updates. Check your logs weekly.

A locked-down server won't make headlines. That's the point.

FAQ

How often should I review server hardening settings?

Review quarterly or after any major software updates. Check user accounts, firewall rules, and audit logs monthly.

Can I automate these hardening steps?

Yes. Configuration management tools like Ansible, Puppet, or Chef let you apply these settings across multiple servers consistently.

Will SELinux or AppArmor break my applications?

They might block unexpected behavior initially. Check logs, adjust policies, or fix application issues rather than disabling the protections.

Do I need all twelve steps on every server?

Prioritize based on exposure. Public-facing servers need the full checklist. Internal systems behind a firewall can skip some network hardening but should still restrict user access and monitor logs.