Every time a new VPS boots, it is already reachable from the entire internet. Within minutes, automated scanners are probing port 22 trying default credentials. This guide is the checklist I run through on every fresh box — tested on Ubuntu 24.04 LTS, applicable with minor adjustments to any Debian-based distro.
Work through the sections in order. Each step takes two minutes or less, and together they close the gaps that the vast majority of real-world attacks exploit.
1. Create a non-root user
Providers give you root by default. The first thing to do is make a normal user and switch to it permanently.
adduser deploy
usermod -aG sudo deploy
Log out, reconnect as deploy, and confirm sudo works before you continue:
sudo whoami
# should output: root
Never log in as root again. If you need elevated access, use sudo.
2. Set up SSH keys and disable password login
Password login is the main attack surface automated bots target. Replace it with key-based authentication.
Generate a key pair on your local machine if you do not have one already:
ssh-keygen -t ed25519 -C "[email protected]"
Copy the public key to the server:
ssh-copy-id deploy@YOUR_SERVER_IP
Test that key login works, then open /etc/ssh/sshd_config on the server and set:
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
Restart SSH — but keep your current session open while you test in a second window:
sudo systemctl restart ssh
Open a new terminal and confirm you can still log in with your key before closing the original session.
3. Configure a firewall
UFW (Uncomplicated Firewall) ships with Ubuntu and gives you a readable default-deny policy in a few commands.
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
Run the allow OpenSSH line before ufw enable, or you will lock yourself out. Add only the ports your services actually need — everything else stays closed.
4. Enable automatic security updates
Unpatched packages are how most servers get compromised weeks or months after initial setup. Let the OS handle security patches automatically.
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Verify it is active:
sudo systemctl status unattended-upgrades
The default configuration installs security updates only, leaving feature updates for you to apply manually. That is the right balance — you get patches without unexpected breakage.
5. Install Fail2ban
Even with SSH keys enabled, bots still hammer every open port. Fail2ban reads service logs and bans IPs that trigger too many failures, reducing noise and protecting other services alongside SSH.
sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local to set sensible defaults in the [DEFAULT] section:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
Enable and start it:
sudo systemctl enable --now fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
6. Reduce attack surface
The fewer things exposed, the smaller the target. Two quick wins:
Close unused ports. List what is currently listening:
sudo ss -tulnp
Disable services you do not need. Common candidates on a fresh Ubuntu install:
sudo systemctl stop avahi-daemon
sudo systemctl disable avahi-daemon
Do not expose app ports directly. Run applications on localhost and put them behind a reverse proxy that handles HTTPS termination. This means your app never touches the public internet directly, and you only need ports 80 and 443 open. See the Nginx Proxy Manager setup guide for a straightforward way to do this.
Bonus: should you change the SSH port?
Moving SSH from port 22 to something like 2222 or 51022 is a common recommendation. The honest take: it eliminates a large volume of automated scanner noise from your logs, which makes it easier to spot real events. It is not a security control — any port scanner finds your SSH within seconds. Do not rely on it instead of key auth and a firewall; do it in addition if log cleanliness matters to you.
If you do change it, allow the new port in UFW before restarting SSH:
sudo ufw allow 2222/tcp
Then update Port 2222 in /etc/ssh/sshd_config and restart.
Troubleshooting and gotchas
Four situations that trip people up:
Locked out after disabling password auth. Always keep an existing session open while you test changes. If you do get locked out, most providers offer a web console or rescue mode that bypasses SSH entirely — use that to re-enable password auth temporarily, then fix the issue properly.
UFW blocked SSH. This happens when you run ufw enable before adding the SSH allow rule. Fix it via the provider console: disable UFW (ufw disable), add the SSH rule, then re-enable.
Key auth fails even after ssh-copy-id. The most common cause is wrong permissions. On the server, the ~/.ssh directory must be 700 and ~/.ssh/authorized_keys must be 600, both owned by your user:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Fail2ban banned yourself. Check whether your IP is banned:
sudo fail2ban-client status sshd
Unban it:
sudo fail2ban-client set sshd unbanip YOUR_IP
Add your home IP to the ignoreip line in jail.local to prevent this recurring.
After hardening
With the basics in place, the natural next steps are:
- Put services behind HTTPS with Nginx Proxy Manager
- Choose a reliable host in Best VPS for Self-Hosting
- Point a domain at your server: Point a domain to a VPS
For host recommendations: Hetzner offers excellent value for European and US regions; Vultr has a developer-friendly cloud with solid global coverage.