A new public server starts receiving SSH brute-force attempts within minutes. Hardening is not a project; it is a checklist you run every time, before the first application is deployed. This is mine for Ubuntu 24.04 LTS.
1. Users and SSH
- Create a personal user with sudo rights and log in with an SSH key (ed25519).
- In
/etc/ssh/sshd_configsetPermitRootLogin noandPasswordAuthentication no, then reload SSH from a second session so you cannot lock yourself out.
2. Firewall
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
Important: Docker publishes container ports through its own iptables rules and bypasses UFW. Bind internal services to 127.0.0.1 in your compose file, or filter them in the DOCKER-USER chain.
3. Updates
Install unattended-upgrades so security patches are applied automatically, and schedule a monthly window for the rest, including kernel reboots.
4. Brute-force protection
fail2ban with the default sshd jail bans IPs that keep failing. Add jails for your web application login if it logs failed attempts.
5. Reduce the attack surface
- List listening ports with
ss -tulpnand remove or disable anything you do not need. - Databases and Redis listen on localhost only.
- Secrets live in environment files readable only by the service user, never in Git.
6. Logs, time and backups
Keep time synced (systemd-timesyncd), ship important logs off the server, and back up both data and configuration to a different provider. A backup is only real after you have restored it once.
7. Write it down
Turn the checklist into a script or an Ansible playbook and keep it in Git. The second server takes five minutes, and every server is hardened the same way.