Applies when something runs on a virtual machine I administer. This annex adds to baseline-runbook.md and annex-tier2.md.
First question every time: does this need a VPS at all? A managed platform removes most of this page's work and most of its risk. I only take on a VPS when the client's constraints genuinely require it, and I say who is responsible for patching it after handover before it is built. An unowned VPS is a liability with a monthly bill.
The host is in an EU/EEA region and runs a current LTS distribution.
SSH keys only
- Password authentication off, root login off. In
sshd_config:PasswordAuthentication no,PermitRootLogin no,PubkeyAuthentication yes. - Work happens as a non-root user with
sudo. - Ed25519 keys, passphrase-protected, one key per machine I connect from. No shared keys, no key pasted anywhere, no agent forwarding to the box.
- Provider console access is a separate account with MFA on — that is the way back in if SSH breaks, and it is also a way in for anyone who phishes me.
- Verify: after changing the config, open a second session before closing the first, then confirm
ssh -o PubkeyAuthentication=no user@hostis refused. - At handover: my key is removed from
authorized_keysand I confirm I can no longer log in.
Firewall
- Default deny inbound. Open only what is needed: SSH, and HTTPS if the box actually serves something.
- Nothing else is exposed. Databases, admin panels and job runners bind to
127.0.0.1, not0.0.0.0— a firewall rule and a bind address are two different protections and I want both. - SSH restricted by source IP where the client has a fixed range.
- Outbound restricted where practical, so a compromised process cannot casually reach the internet.
- Verify: check the rule list (
ufw status verbose), check what is actually listening (ss -tulpn), then scan the host from outside and confirm the open ports match the intended list — not what I meant to configure, what is reachable.
Automatic patching
- Unattended security upgrades on, with automatic reboot in a stated maintenance window if the client accepts it. Kernel patches that need a reboot are worthless until the reboot happens.
- Application dependencies pinned with a lockfile, Dependabot on the repo, and security updates applied rather than queued.
- Unused packages and services removed — the smallest install is the easiest to patch.
- Verify: check the upgrade log monthly and confirm updates are landing. Confirm uptime is not longer than the patch cycle, because a very long uptime means reboots are not happening.
- Whoever owns patching after handover is named in
templates/handover.md.
fail2ban
- Enabled for SSH at minimum, plus the web server if one is exposed.
- Sensible bans on repeated auth failures — this is noise reduction, not a security control on its own. Key-only SSH is what actually protects the box.
- My own source range allowlisted so I do not lock myself out.
- Verify: check
fail2ban-client status sshdafter a week and confirm the jail is active and has counted something. A jail with zero activity on an internet-facing host usually means it is not really watching.
The rest of the baseline still applies
Disk encryption where the provider offers it, secrets injected at runtime rather than sitting in files on the box, logs shipped somewhere they survive the machine dying, backups tested by restoring onto a fresh instance, and the whole host listed in the access register and the data-flow diagram. If the box holds client data, it goes in the Art. 30 register too.