Skip to content
New: managed cloud and dedicated servers — with dedicated support on every plan.See plans
Inventure Technologies

Linux server hardening checklist (2026)

A 14-step Ubuntu/Debian server hardening checklist: updates, SSH, firewall, fail2ban, auditd and backups, with commands to verify each control.

IInventure Engineering Team6 min read
On this page

A Linux server hardening checklist turns "secure the server" into a short, ordered list of controls you can actually finish in an afternoon: patch automatically, lock down SSH, turn on a firewall, block brute-force attempts, limit who can do what, and log the changes that matter. Below is that checklist for Ubuntu and Debian servers, in the order we'd apply it on a fresh box, with the command to verify each one actually worked. It pairs with our wider guide to cloud servers, VPS and managed hosting — this post is the "what to do once the server exists" half.

Hardening reduces risk; it isn't a substitute for patching cadence, tested backups or someone watching for incidents. For the operational side of running a server long-term, see security and DevSecOps.

1. Turn on automatic security updates#

Most breaches exploit a known, already-patched vulnerability. unattended-upgrades is installed by default on Ubuntu Server and applies security updates on its own schedule.

bash
sudo apt update && sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
sudo unattended-upgrade --dry-run -d

Check /etc/apt/apt.conf.d/50unattended-upgrades to confirm the -security line is uncommented, and /etc/apt/apt.conf.d/20auto-upgrades shows "1" for both settings.

2. Lock down SSH: keys only, no root login#

This is the single highest-value change on the list. Edit /etc/ssh/sshd_config:

bash
# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers deploy admin

Keep your current session open, test the syntax, then restart:

bash
sudo sshd -t
sudo systemctl restart ssh

Verify by opening a new terminal and confirming a fresh connection still works before you close the old session. Moving SSH off port 22 is optional and only cuts scanner noise — it is not a substitute for the settings above.

3. Turn on a firewall (ufw or nftables)#

A default-deny inbound firewall stops everything you haven't explicitly allowed.

bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Allow SSH before enabling, or you will lock yourself out. On Ubuntu 20.10 and later, ufw's rules are enforced through an nftables backend, so avoid hand-writing separate nftables rules alongside it — Ubuntu's security documentation recommends picking one tool.

4. Block brute-force attempts with fail2ban#

fail2ban watches your logs and temporarily bans IPs after repeated failed logins.

bash
sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
bash
# /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 3
findtime = 10m
bantime = 1h
bash
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

Never edit jail.conf directly — package updates overwrite it; jail.local is read on top of it.

5. Set up least-privilege users and sudo#

Nobody, including you, should work as root day to day.

bash
sudo adduser deploy
sudo usermod -aG sudo deploy
sudo visudo

Verify with sudo -l -U deploy to see exactly what a user can run, and lock accounts that leave the team with sudo passwd -l <user> rather than deleting them mid-investigation.

6. Audit running services and open ports#

Every running service is something you have to patch and something an attacker can try. Remove what you don't need.

bash
systemctl list-units --type=service --state=running
sudo ss -tulpn
sudo apt remove --purge <unused-package>

Re-run ss -tulpn after each change and confirm only the ports you expect (SSH, web, database if remote) are listening.

7. Synchronise the clock#

Accurate time matters for TLS certificate validation, log correlation across servers, and cron jobs firing when you expect. Ubuntu 24.04 LTS defaults to systemd-timesyncd; Ubuntu 25.10 and newer default to chrony, which handles patchy connectivity better and is worth installing on any server you rely on for accurate timestamps.

bash
timedatectl status
sudo apt install chrony -y
chronyc tracking

Verify timedatectl status reports System clock synchronised: yes.

8. Turn on logging and auditd#

Application logs show what your app did. auditd shows who touched the operating system: file edits, new users, privilege escalation.

bash
sudo apt install auditd audispd-plugins -y
sudo systemctl enable --now auditd
bash
# /etc/audit/rules.d/hardening.rules
-w /etc/passwd -p wa -k identity
-w /etc/ssh/sshd_config -p wa -k sshd_config
bash
sudo augenrules --load
sudo ausearch -k sshd_config

9. Add file integrity monitoring#

AIDE takes a snapshot of file hashes and permissions, then tells you what changed since.

bash
sudo apt install aide aide-common -y
sudo aideinit
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
sudo aide --check

Schedule the check on a cron job and copy the baseline database somewhere off the server — a database an attacker can also edit isn't proof of anything.

10. Configure backups before you need them#

A hardened server that isn't backed up is still one disk failure or one mistake away from data loss. Set up automated, encrypted, off-server backups and — this is the part people skip — actually restore from one to prove it works. We cover the full method, including the 3-2-1 rule and a restore drill, in backups that actually restore.

11. Enforce TLS everywhere#

TLS 1.0 and 1.1 were formally deprecated by the IETF in RFC 8996; use TLS 1.2 as a floor and prefer 1.3.

nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;

Automate renewal with certbot (or your provider's ACME client) and verify it before you need it:

bash
sudo certbot renew --dry-run

12. Keep secrets out of code and images#

Database passwords, API keys and signing certificates should never sit in a Git repository or a container image layer.

bash
chmod 600 .env

Use environment variables injected at deploy time or a secrets manager, rotate anything a departing team member could have seen, and grep your Git history for accidental commits (git log -p | grep -i "api_key" as a rough first pass).

13. Apply sysctl network hardening#

A handful of kernel network parameters close off common attack classes with no application changes.

bash
# /etc/sysctl.d/99-hardening.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.rp_filter = 1
kernel.randomize_va_space = 2
bash
sudo sysctl --system
sudo sysctl net.ipv4.tcp_syncookies

Full parameter definitions are in the kernel's own sysctl documentation — read what a setting does before you copy it onto a router or a host that genuinely needs forwarding.

14. Add monitoring and alerting#

Hardening tells you a server is configured safely today. Monitoring tells you when that stops being true — a service dies, disk fills up, or login attempts spike. A minimal stack (uptime checks, resource alerts, log-based alerting) is enough for most small teams; see monitoring vs observability for small teams for how to build one without alert fatigue.

Linux server hardening checklist: control, why, how to verify#

Control

Why it matters

How to verify

Automatic updates

Closes known vulnerabilities without waiting on a human

sudo unattended-upgrade --dry-run -d

SSH keys only, no root login

Blocks the most common automated attack path

New session connects after restart; password login is refused

Firewall (ufw/nftables)

Default-deny stops unplanned exposure

sudo ufw status verbose

fail2ban

Slows and blocks brute-force attempts

sudo fail2ban-client status sshd

Least-privilege users/sudo

Limits blast radius of a compromised account

sudo -l -U <user>

Service audit

Fewer running services, smaller attack surface

sudo ss -tulpn

Time sync

Correct logs, valid TLS checks, reliable cron

timedatectl status

Logging/auditd

Tells you who changed what on the OS

sudo ausearch -k <key>

File integrity (AIDE)

Detects unexpected file changes

sudo aide --check

Backups

Recovery when everything else fails

A completed restore drill

TLS everywhere

Encrypts data in transit, meets current standards

sudo certbot renew --dry-run

Secrets management

Stops credential leaks via code or images

.env not in git status; permissions 600

sysctl hardening

Closes common network-level attack classes

sudo sysctl net.ipv4.tcp_syncookies

Monitoring/alerting

Tells you when a hardened server stops being healthy

An alert fires in a test

What to do next#

Work through the checklist in order on any server you manage directly, and re-run the verification commands rather than trusting that a setting "should" be applied. If you'd rather have this done and kept up to date for you — including the monitoring, patching cadence and restore testing — see how we handle it on managed cloud servers, or view current plans and prices.

Frequently asked questions

Is ufw enough, or do I also need iptables or nftables?

For most single-server setups, ufw is enough. On Ubuntu 20.10 and later, ufw's rules are enforced through an nftables backend, so you already get nftables underneath. Write raw nftables rules only for logic ufw can't express, and avoid running both at once — Ubuntu's own security documentation warns against mixing them.

Should I move SSH off port 22?

It cuts down log noise from opportunistic scanners, but it is not a real security control by itself. Keys-only authentication, no root login and fail2ban do the actual work. Treat a custom port as an optional extra, never a replacement for those three.

How often should I redo this checklist?

Run through it on every new server, after any major OS upgrade, and at least once or twice a year as a review. Re-run the verification commands rather than trusting memory — configuration drifts as people and packages change.

Do I need auditd if my application already logs everything?

Yes. Application logs show what your app did; auditd shows who touched the operating system itself — edited a config file, added a user, escalated privileges. You need both to reconstruct an incident properly.

What's a fast way to check how hardened a server already is?

Lynis (open source, from CISOfy) is a good first pass: it scans the system and returns a hardening index with specific, numbered suggestions. Treat it as a starting checklist, not a certification — you still need to verify each fix yourself.

Sources

  1. Automatic updates — Ubuntu Server documentation — accessed 18 September 2026
  2. UFW — Ubuntu Community Help Wiki — accessed 18 September 2026
  3. nftables — Ubuntu security documentation — accessed 18 September 2026
  4. sshd_config(5) — OpenBSD manual pages — accessed 18 September 2026
  5. CIS Ubuntu Linux Benchmarks — Center for Internet Security — accessed 18 September 2026
  6. auditd(8) — Linux manual page — accessed 18 September 2026
  7. AIDE Manual — accessed 18 September 2026
  8. About time synchronisation — Ubuntu Server documentation — accessed 18 September 2026
  9. fail2ban — GitHub repository — accessed 18 September 2026
  10. RFC 8996: Deprecating TLS 1.0 and TLS 1.1 — accessed 18 September 2026
  11. IP Sysctl — The Linux Kernel documentation — accessed 18 September 2026
  12. Lynis — GitHub repository (CISOfy) — accessed 18 September 2026

Facts in this article were last checked on 18 September 2026.

I

Inventure Engineering Team

Engineers at Inventure Technologies who build, host and run software for clients in Nepal and Australia. We write about what we do every day.

Keep reading

Want engineers who handle this for you?

We build, host and run software for teams in Nepal and Australia — with dedicated support on every plan.