Put a server on a public IP and it will start receiving unsolicited SSH login attempts almost immediately. None of it means anyone is targeting you specifically. It is background noise from automated scanners sweeping the entire IPv4 space, continuously. The good news is that this noise is defeated almost entirely by a handful of standard changes.
Work through this in order. It takes about half an hour on a fresh Debian or Ubuntu install.
1. Update everything before you do anything else
Your image was built at some point in the past. Patch it before you expose any services.
sudo apt update && sudo apt upgrade -y sudo reboot
2. Create a non-root user
Working as root full-time means every typo is potentially unrecoverable and every compromised process is a full compromise. Create a user with sudo rights and use that instead.
adduser deploy usermod -aG sudo deploy
3. Switch to SSH keys and disable password login
This is the single highest-value change on the list. A password can be guessed; a 4096-bit key can’t be, in any practical sense. Generate the key on your local machine, not on the server:
# On your laptop, not the server ssh-keygen -t ed25519 -C "vmfrost-vps" ssh-copy-id [email protected]
Confirm you can log in with the key in a second terminal before you lock the door behind you. Then edit /etc/ssh/sshd_config:
PermitRootLogin no PasswordAuthentication no ChallengeResponseAuthentication no PubkeyAuthentication yes
sudo systemctl restart ssh
Keep your existing SSH session open until you have verified the new one works. If you lock yourself out, console access is the only way back in.
4. Turn on a firewall with a default-deny policy
Default-deny means anything you haven’t explicitly allowed is blocked. This protects you from services you forgot were listening.
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow OpenSSH sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status verbose
5. Install fail2ban
With password auth disabled the brute-force attempts can’t succeed, but they still consume resources and clutter your logs. fail2ban bans repeat offenders at the firewall level.
sudo apt install fail2ban -y sudo systemctl enable --now fail2ban sudo fail2ban-client status sshd
6. Enable unattended security upgrades
Most real-world compromises exploit a known vulnerability that had a patch available. Automating security patches closes that window without requiring you to remember.
sudo apt install unattended-upgrades -y sudo dpkg-reconfigure --priority=low unattended-upgrades
7. Audit what is actually listening
You may be surprised. Databases in particular have a habit of binding to 0.0.0.0 by default.
sudo ss -tulpn
Anything that only needs to be reachable by other processes on the same box should bind to 127.0.0.1 instead. A firewall is a second line of defence, not a substitute for correct bind addresses.
What this doesn’t cover
This checklist secures the server. It doesn’t secure what you run on it. An outdated CMS, a leaked API key in a public repository, or a web application with an injection flaw will all get you compromised through a port you deliberately left open. Treat application security as a separate and ongoing job.
- Keep application dependencies patched, not just OS packages.
- Never commit secrets to version control; use environment variables or a secrets manager.
- Put TLS in front of anything serving traffic. Certificates are free via Let’s Encrypt.
- Take backups and test restoring them, because hardening reduces risk rather than eliminating it.