Skip to content
Back to Blog
Security11 min read

Server Security Hardening Checklist: 9 Steps for 2026

Lock down your Linux server with SSH keys, firewall rules, automated patching, and service audits that actually prevent break-ins.

Written by Abdul AbrorTechnical Hosting Support Engineer
Server Security Hardening Checklist: 9 Steps for 2026
On this page

Most compromised servers share the same weak spots: default SSH settings, open ports no one uses, and packages that haven't seen an update in months. I've rebuilt dozens of boxes after attacks, and the pattern is always similar—one exposed service, one old exploit, one late night.

This checklist walks you through the nine steps that actually stop intrusions. You'll disable what you don't need, lock down remote access, automate updates, and build firewall rules that make sense. No theory—just commands and config files.

1. Audit and disable unused services

Every service listening on a port is a potential entry point. Start by listing what's currently running:

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

You'll see everything from sshd to cups to avahi-daemon. Most servers don't need Bluetooth managers, printer services, or Avahi mDNS responders. Disable anything you don't recognize or use:

sudo systemctl stop cups
sudo systemctl disable cups

Check which ports are open:

sudo ss -tulnp

For each listening port, trace it back to the service. If you're running a web server, you need 80 and 443. SSH needs 22 (or your custom port). Close everything else. In support tickets I handled, the usual culprit was a development database left exposed on 0.0.0.0 instead of 127.0.0.1.

Disable services at boot so they don't come back after a reboot. Confirm with another systemctl list-units pass.

2. Enforce SSH key authentication

Password-based SSH is how most brute-force attacks succeed. Switch to key-based auth and your attack surface shrinks to near zero.

Generate an ED25519 key pair on your local machine (not the server):

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

Copy the public key to the server:

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip

Test the key login in a separate terminal before you disable passwords—if the key doesn't work and you lock yourself out, you'll need console access to fix it.

Once key login works, edit /etc/ssh/sshd_config:

PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password

Restart SSH:

sudo systemctl restart sshd

Root should never log in directly, even with a key. Create a sudo user for admin work and block root SSH entirely by changing PermitRootLogin to no once you've confirmed your sudo user works.

3. Change the default SSH port

Port 22 gets hammered by automated scanners around the clock. Moving SSH to a high port (above 1024, below 65535) drops that noise to zero.

Pick a port that doesn't conflict with anything else—49222 or 52845 work fine. Edit /etc/ssh/sshd_config:

Port 52845

If you're running a firewall (and you will be in step 4), open the new port before restarting SSH, or you'll lock yourself out:

sudo ufw allow 52845/tcp
sudo systemctl restart sshd

Connect with:

ssh -p 52845 user@your-server-ip

Security through obscurity isn't a defense on its own, but it eliminates thousands of pointless login attempts and keeps your auth logs clean.

4. Configure a firewall (UFW or firewalld)

A firewall blocks everything except the ports you explicitly allow. On Ubuntu and Debian, UFW is the easiest option. On CentOS and RHEL, use firewalld.

UFW setup

Install and enable UFW:

sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing

Allow your custom SSH port and any services you actually run:

sudo ufw allow 52845/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Enable the firewall:

sudo ufw enable

Check the rules:

sudo ufw status verbose

firewalld setup

On CentOS/RHEL:

sudo systemctl start firewalld
sudo systemctl enable firewalld
sudo firewall-cmd --permanent --add-port=52845/tcp
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Both tools persist across reboots. Don't open ports "just in case"—if you're not sure you need it, leave it closed.

5. Automate security updates

Unpatched software is the second-most common entry point after weak SSH. Automate the boring stuff so critical patches land within hours, not weeks.

Debian/Ubuntu: unattended-upgrades

Install the package:

sudo apt install unattended-upgrades

Edit /etc/apt/apt.conf.d/50unattended-upgrades and make sure security updates are enabled:

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

Enable automatic runs:

sudo dpkg-reconfigure --priority=low unattended-upgrades

Check logs in /var/log/unattended-upgrades/ to confirm updates are running.

CentOS/RHEL: dnf-automatic

Install and configure:

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

Edit /etc/dnf/automatic.conf and set apply_updates to yes under the [commands] section. The timer runs daily by default.

Automated updates won't break things if you're running stable packages from official repos. Custom or third-party software should be tested in staging first.

6. Disable root login and create sudo users

Root has unrestricted access to everything. If an attacker gets root credentials, it's over. Disable direct root login and use sudo instead.

Create a new user:

sudo adduser adminuser
sudo usermod -aG sudo adminuser

On CentOS/RHEL, the group is wheel instead of sudo:

sudo usermod -aG wheel adminuser

Test sudo access:

su - adminuser
sudo whoami

If that returns root, you're good. Now disable root login completely in /etc/ssh/sshd_config:

PermitRootLogin no

Restart SSH and confirm you can still log in as your sudo user. Root can still be accessed locally via console if needed.

7. Install and configure Fail2Ban

Fail2Ban monitors auth logs and bans IP addresses after repeated failed login attempts. It's a last line of defense if someone finds your SSH port.

Install:

sudo apt install fail2ban

Create a local config in /etc/fail2ban/jail.local:

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

Restart Fail2Ban:

sudo systemctl restart fail2ban

Check banned IPs:

sudo fail2ban-client status sshd

After three failed attempts in ten minutes, the source IP is blocked for an hour. You can adjust maxretry, bantime, and findtime to be more or less aggressive. I keep maxretry at 3—legitimate users don't need more than that.

8. Enable and review system logs

Logs tell you what's happening before something breaks. Enable persistent journald logging so you can review auth attempts, service failures, and kernel messages even after a reboot.

Edit /etc/systemd/journald.conf:

Storage=persistent

Restart journald:

sudo systemctl restart systemd-journald

Review recent auth attempts:

sudo journalctl -u ssh -n 50

Check for failed logins:

sudo journalctl _SYSTEMD_UNIT=sshd.service | grep -i failed

Set up log rotation in /etc/logrotate.d/ if your logs grow fast. Most distros rotate automatically, but check /var/log/ disk usage with du -sh /var/log/ to be sure.

9. Run periodic security scans with Lynis

Lynis is an open-source auditing tool that scans your system and flags misconfigurations. It's not a fix-everything button, but it catches things you missed.

Install:

sudo apt install lynis

Run a full audit:

sudo lynis audit system

The report appears in /var/log/lynis.log and on-screen. Lynis checks file permissions, kernel parameters, open ports, installed packages, and more. Fix the high-priority warnings first—things like world-writable files, weak SSH ciphers, or missing security modules.

Run Lynis monthly or after major config changes. The scan takes a few minutes and the suggestions are usually worth implementing.

What happens if you skip these steps?

Skip SSH hardening and your server becomes a brute-force target within hours. Leave unused services running and you're exposed to every CVE that service has. Forget patch automation and you'll be running vulnerable software for weeks.

I've seen WordPress sites compromised through an outdated PHP package, mail servers hijacked because Postfix was exposed on 0.0.0.0, and web panels breached via default SSH on port 22 with password auth. Every single case could have been prevented with this checklist.

Security isn't a one-time setup. Run Lynis quarterly, review auth logs weekly, and keep an eye on your firewall rules as you add services. The time you spend now saves you from rebuilding later.

FAQ

Do I need to change the SSH port if I'm using key-only auth?
Key-only auth is secure by itself, but changing the port eliminates noise from automated scanners and keeps your logs readable. It takes two minutes.

Will automated updates break my server?
Not if you're using official distro repos. Security updates are tested before release. Custom software or third-party repos should be handled separately.

How do I undo a Fail2Ban block?
Unban an IP with sudo fail2ban-client set sshd unbanip 1.2.3.4. Check the ban list first with sudo fail2ban-client status sshd.

Can I run this checklist on a VPS with limited RAM?
Yes. UFW, Fail2Ban, and unattended-upgrades use minimal resources. Lynis only runs when you call it.

What if I lock myself out during SSH hardening?
Use your VPS provider's console access (usually a web-based terminal) to undo changes. Always test SSH keys in a second terminal before disabling password auth.

Your next security audit

Walk through this checklist on every new server and revisit it quarterly on existing boxes. Services change, packages update, and firewall rules drift. An audit takes less than an hour and catches configuration rot before it becomes a problem.

Start with step one and work down the list. If something fails, check your logs first—journalctl and /var/log/ will point you to the issue. Security isn't about perfection; it's about making your server harder to compromise than the next one.