Skip to content
Back to Blog
Security10 min read

Server Security Hardening Checklist: 10 Steps [2026]

A ten-step checklist covering SSH keys, firewall rules, automatic updates, fail2ban, and log monitoring to lock down your Linux server.

Written by Abdul AbrorTechnical Hosting Support Engineer
Server Security Hardening Checklist: 10 Steps [2026]
On this page

Most server breaches happen because someone skipped the basics. You patch the application but forget the SSH port is wide open with password auth. Or you lock down SSH but never configure a firewall. Every missed step is an open door.

This checklist walks through ten hardening steps I've used on production VPS and dedicated servers. They're not exotic. They work because they close the gaps attackers scan for first.

1. Disable root SSH login and use key-based authentication

Password authentication over SSH is the easiest vector for brute-force. Disable it entirely.

Generate an SSH key pair on your local machine if you haven't already:

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

Copy the public key to your server:

ssh-copy-id user@your_server_ip

Then edit /etc/ssh/sshd_config and set:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Restart SSH:

systemctl restart sshd

Test the new connection in a separate terminal before closing your current session. If something breaks, you still have access to fix it.

2. Change the default SSH port

Port 22 gets hammered by automated scanners. Moving SSH to a non-standard port (like 2222 or 49222) drops that noise to nearly zero.

In /etc/ssh/sshd_config, change:

Port 2222

Restart SSH and update your firewall rules to allow the new port. When you reconnect, specify the port:

ssh -p 2222 user@your_server_ip

This isn't security through obscurity alone—it's reducing attack surface. Bots scan port 22. They rarely scan all 65,535 ports.

3. Configure a firewall with UFW or firewalld

A firewall blocks everything except the services you explicitly allow. On Ubuntu and Debian, UFW is the simplest choice. On RHEL-based systems, use firewalld.

UFW example:

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

Check the status:

ufw status verbose

firewalld example:

firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

If you changed the SSH port, add it as a custom rule:

firewall-cmd --permanent --add-port=2222/tcp
firewall-cmd --reload

Don't skip this step. I've seen servers compromised within hours of being online because no firewall was active.

4. Install and configure fail2ban

fail2ban watches your logs and bans IPs that show malicious behavior—repeated failed SSH logins, for example.

Install it:

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

The default config (/etc/fail2ban/jail.conf) should not be edited directly. Copy it:

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

In jail.local, enable the SSH jail and set the ban time:

[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600

Restart fail2ban:

systemctl restart fail2ban

Check banned IPs:

fail2ban-client status sshd

In support tickets I handled, fail2ban routinely blocked hundreds of IPs per week on public-facing servers.

5. Enable automatic security updates

Manual patching is unreliable. Automate it.

On Debian/Ubuntu, install unattended-upgrades:

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

Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic security updates and optionally reboot:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";

On RHEL/CentOS, use dnf-automatic:

yum install dnf-automatic
systemctl enable --now dnf-automatic.timer

Edit /etc/dnf/automatic.conf and set:

apply_updates = yes

Automatic updates won't catch everything, but they close the window for known exploits.

6. Disable unused services and remove unnecessary packages

Every running service is a potential entry point. List active services:

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

Disable anything you don't need:

systemctl disable --now <service_name>

Common candidates: rpcbind, avahi-daemon, cups, bluetooth.

Remove unused packages:

apt autoremove       # Debian/Ubuntu
yum autoremove       # RHEL/CentOS

A smaller attack surface is easier to defend.

7. Set up log monitoring with logwatch or journalctl alerts

Logs tell you what's happening. If you're not reading them, you're flying blind.

logwatch emails you a daily summary:

apt install logwatch

Edit /usr/share/logwatch/default.conf/logwatch.conf and set your email:

MailTo = [email protected]

Run it manually to test:

logwatch --detail high --mailto [email protected] --range today

journalctl can filter logs by priority:

journalctl -p err -b

Set up a cron job to email critical errors:

0 8 * * * journalctl -p crit --since "24 hours ago" | mail -s "Critical logs" [email protected]

Log monitoring catches issues before they become incidents.

8. Configure file integrity monitoring with AIDE

AIDE (Advanced Intrusion Detection Environment) hashes your file system and alerts you when critical files change.

Install it:

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

Initialize the database:

aideinit

On some systems, the command is aide --init. The initial database is written to /var/lib/aide/aide.db.new. Move it:

mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run a check:

aide --check

Schedule daily checks via cron:

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

If an attacker modifies /etc/passwd or installs a backdoor, AIDE will tell you.

9. Restrict user privileges and use sudo properly

Never run applications as root. Create service accounts with minimal permissions.

For human users, grant sudo access only when needed. Edit /etc/sudoers with visudo:

username ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx

This allows username to restart nginx without a password, but nothing else.

For service accounts, disable login:

useradd -r -s /usr/sbin/nologin serviceuser

The -r flag creates a system account. The -s flag sets the shell to nologin, preventing interactive login.

10. Enable SELinux or AppArmor for mandatory access control

SELinux (on RHEL-based systems) and AppArmor (on Debian-based systems) enforce access policies that limit what processes can do, even if they're compromised.

SELinux:

Check the current mode:

getenforce

If it's Permissive or Disabled, set it to Enforcing in /etc/selinux/config:

SELINUX=enforcing

Reboot for the change to take effect. Review denials in /var/log/audit/audit.log.

AppArmor:

Check the status:

aa-status

Enable profiles for installed services:

aa-enforce /etc/apparmor.d/*

Both tools have a learning curve, but they've stopped privilege escalation attacks cold in environments I've managed.

What about intrusion detection systems?

If you're running multiple servers or handling sensitive data, consider an IDS like Snort or Suricata. They monitor network traffic for suspicious patterns.

For single VPS setups, the steps above are sufficient. Most attacks target the low-hanging fruit: weak SSH, no firewall, outdated packages. Close those gaps first.

Automate the checklist with Ansible or shell scripts

Manual hardening works for one server. For five or fifty, automate it.

Here's a minimal shell script that applies steps 1, 3, 4, and 5:

#!/bin/bash
set -e

# Update system
apt update && apt upgrade -y

# Install packages
apt install -y ufw fail2ban unattended-upgrades

# Configure UFW
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable

# Enable fail2ban
systemctl enable --now fail2ban

# Enable unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

echo "Hardening complete. Review SSH config and reboot."

For Ansible, write a playbook that applies the same tasks across an inventory. Version-control the playbook so every server starts with the same baseline.

Start with SSH and the firewall

If you implement only two items from this checklist, make them SSH hardening and firewall configuration. Those two steps block the majority of opportunistic attacks.

The rest—fail2ban, automatic updates, log monitoring, file integrity checks—add depth. They catch what slips through the first line of defense.

Hardening isn't a one-time project. It's a baseline. Review it quarterly, automate what you can, and adjust as your infrastructure grows.

FAQ

How often should I review server security settings?

Quarterly at minimum. After any major software update or configuration change, re-run the checklist. Security is not a one-time task.

Is changing the SSH port really effective?

It won't stop a determined attacker, but it eliminates 99% of automated brute-force attempts. Combined with key-based auth and fail2ban, it's highly effective.

Can I skip automatic updates if I test patches manually?

Only if you have a formal patch management process and apply updates within 24-48 hours of release. Most teams don't. Automate it.

What's the best firewall for a VPS?

UFW for simplicity, firewalld for advanced features, iptables for full control. UFW is usually enough.

Should I enable SELinux in permissive mode first?

Yes. Run it in permissive mode for a week, review the audit logs, and fix any policy issues before switching to enforcing.