Ubuntu server hardening, one step per page
The twenty-four steps that take a fresh Ubuntu VPS from default to defensible, without locking yourself out.
A diagram of the mechanism, the classic mistake, and one thing to go try, on a test session first. That's a page.
Ubuntu server hardening, one step per page
The twenty-four steps that take a fresh Ubuntu VPS from default to defensible, without locking yourself out.
Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).
Commands and settings checked against the Ubuntu Server documentation (firewall, OpenSSH server, AppArmor, automatic updates, user management), the Ubuntu 24.04 LTS manual pages for sshd_config(5), ufw(8) and ufw-framework(8), the official fail2ban documentation, and the Ubuntu security notices. The CIS Ubuntu Linux Benchmark is cited as a reference standard for configuration practice, not quoted. Written and tested against Ubuntu 24.04 LTS; commands may differ on other releases.
General guidance only, for your own servers. You are responsible for changes you make to a production machine: rehearse every step on a test session first. Not professional security advice.
Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.
© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.
Edition 1.0 · stevehodgkiss.net
Contents
Before you touch anything
The moves that cost nothing and save everything: snapshots before changes, a test session instead of a live experiment, updates as a habit, one server one job, and knowing what your logs look like on a normal day.
- 01Snapshot before you change anything
- 02The test-session rule
Take a snapshot of the whole machine before every risky change. A mistake becomes a five-minute restore instead of a rebuild.
Snapshot before you change anything
Before every risky change, freeze the whole machine. A snapshot is the disk at a moment in time: any mistake becomes a five-minute restore.
Without one, a lockout means rebuilding the server, a new IP, and moving your app by hand.
Snapshot, change, test, delete. Under ten minutes on most providers.
The seatbelt you wear while changing the locks.
Snapshot your server in the provider panel, note its name, make one change from this book, test it, then delete the snapshot.
Every step in this book gets rehearsed on a throwaway machine first. Production is not the place to learn a command for the first time.
The test-session rule
Spin up the cheapest disposable Ubuntu 24.04 VM and rehearse each step there first.
The mistake is a first attempt on the box your app lives on. The famous lockouts are nearly all first attempts.
After one rehearsal you paste exact commands in the exact order, already knowing what each prints.
Burn the test VM so production never burns.
Create a one-dollar disposable Ubuntu 24.04 VM, rehearse one step from this book, then destroy it. You practised recovery, not just read it.
Access
The front door: keys instead of passwords, root login and password auth switched off in the safe order, config edits that are tested before they are applied, and a second factor on the control panel.
- 01SSH keys, not passwords
- 02Turn off root login and passwords: the safe order
A key pair replaces the password with two files: a public key that acts as the lock on the server, and a private key that stays on your laptop. Nothing guessable is ever sent.
SSH keys, not passwords
ssh-keygen -t ed25519 makes the pair; ssh-copy-id installs the public half on the server.
The password alternative is exactly what the bots grind at: every password ever typed can be guessed.
After the switch, logins are instant and the guessing simply fails. Ubuntu recommends ed25519: shorter, faster, as strong.
Give the server a lock. Keep the only key in your pocket.
On your laptop: ssh-keygen -t ed25519, then ssh-copy-id you@your-server. Log in from a NEW terminal: no password prompt means the key works.
Once the key works, turn off the doors you no longer need: root login first, then password authentication. The order is the safety, and the old session stays open until the new one is proven.
Turn off root login and passwords: the safe order
Ubuntu limits root to keys already, but say it: PermitRootLogin no, then PasswordAuthentication no.
The reverse is the lockout: passwords off before proving the key, or logging out before testing a fresh login.
Prove the key twice, edit, sshd -t, restart, log in from a new terminal. Then close the old session.
On the test VM: prove key login in a second terminal, add both settings, sudo sshd -t, restart ssh, then log in from a third. Production after the rehearsal passes.
The perimeter
What the internet is allowed to reach: a default-deny firewall enabled the safe way, only the ports your app needs, a guard that counts failed knocks, slower guessing, and the cloud firewall closed too.
- 01Enable the firewall the safe way
- 02fail2ban: the guard that counts knocks
ufw, Ubuntu's firewall, ships installed but switched off. Allow OpenSSH first, then run ufw enable. From then on, incoming traffic is denied unless you named the door, and your session survives the switch.
Enable the firewall the safe way
Without a firewall, every listening service is reachable from anywhere. ufw enable turns on default deny: incoming is refused unless a rule names it.
The famous mistake is enable before allow. Enable slams every door at once, including the one you are standing in.
Two commands in the right order, then sudo ufw status verbose to read the policy. Rules survive reboots.
Name the door you are standing in, then close all the others.
On the test VM: sudo ufw allow OpenSSH, then sudo ufw enable, answer y. Your session survives, and status verbose reads default deny.
fail2ban reads the login log, counts failed attempts from each address, and bans anyone who fails too many times in a row, by writing a firewall rule that rejects them.
fail2ban: the guard that counts knocks
The bots never stop; fail2ban makes them expensive. The sshd jail is on by default: five failures in ten minutes earns a ten minute ban.
Editing jail.conf is the mistake: a package update overwrites it. Yours belong in jail.local.
fail2ban-client status sshd shows the guard on duty: failed, banned, current list.
Install fail2ban on the test VM, run fail2ban-client status sshd, then add ignoreip for your own address in /etc/fail2ban/jail.local so you can never ban yourself.
Work as your own user and borrow root power with sudo, one command at a time. Root all day means every typo and every exploit runs with every key on the ring.
Least privilege: sudo, not root
Ubuntu disables the root password and gives your user sudo. The design: commands run powerless, sudo promotes exactly one when needed.
sudo su - and staying root means every typo runs with every key on the ring.
Log in as you, sudo the one command, return. The CIS standard calls it least privilege.
If a second person helps: sudo adduser sam, sudo usermod -aG sudo sam, sam runs sudo -l. No -a with -G wipes groups: check with groups sam.
Three copies of anything you cannot lose, on two different media, with one offsite. For a server: the live copy, a provider snapshot, and database dumps pulled off the provider entirely. Then restore one, to prove the chain works.
Backups: 3-2-1 for servers
Hardening narrows the odds; backups cap the loss. The rule of thumb: three copies, two media, one offsite, so no single failure takes them all.
The provider snapshot alone is the trap: a compromised panel or billing surprise takes both copies together.
Nightly dumps pulled off the provider, and a monthly restore rehearsed on the test VM.
Add a nightly database-dump cron on the server, scp the latest dump to your laptop, and restore it into the test VM. If the app runs there, the chain is real.