A freshly provisioned Ubuntu VPS on Hetzner, DigitalOcean, or Linode gets its first SSH brute force attempt within minutes of going live. I have logged it. 145 failed login attempts in the first week on a default setup. Here is how to make your server boring to attackers.
This guide assumes Ubuntu 22.04 or 24.04. Most steps apply to Debian too. The shift toward self-hosting after the Vercel/Heroku pricing jumps means more small businesses are now managing their own VPS, often for the first time. This is the checklist I go through every time I set up a new server.
Step 1: Create a non-root user immediately
Your VPS provider gives you root access on first login. The first thing to do is create a regular user with sudo privileges and then disable root SSH. Root is the highest-privilege account and every bot on the internet knows to try it.
# Create a new user (replace 'deploy' with your preferred username)
adduser deploy
# Add the user to the sudo group
usermod -aG sudo deploy
# Switch to the new user
su - deployStep 2: Set up SSH key authentication
Password authentication for SSH should be disabled entirely. An SSH key pair is cryptographically immune to brute-force. Generate a key on your local machine if you do not have one:
# On your LOCAL machine, generate an Ed25519 key (preferred over RSA)
ssh-keygen -t ed25519 -C "your@email.com"
# Copy your public key to the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your-server-ipOnce you have confirmed you can log in with the key, disable password authentication in /etc/ssh/sshd_config:
# /etc/ssh/sshd_config - edit these lines:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
# Restart SSH after editing
sudo systemctl restart sshdWarning
Make absolutely sure your key-based login works before you disable password auth. Open a second terminal and test the key login first. If you lock yourself out, you will need console access through your provider's web panel.
Step 3: Set up UFW firewall
UFW (Uncomplicated Firewall) is the easiest way to manage iptables rules on Ubuntu. The default policy should be deny all incoming, allow all outgoing, then explicitly open only the ports you need.
# Install UFW if not present
sudo apt install ufw
# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (do this BEFORE enabling UFW or you will lock yourself out)
sudo ufw allow 22/tcp
# Allow HTTP and HTTPS if you are running a web server
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable the firewall
sudo ufw enable
# Check status
sudo ufw status verboseIf you changed your SSH port (a minor security measure, but it does reduce noise), substitute 22/tcp with your actual port.
Step 4: Install and configure Fail2ban
Fail2ban monitors log files and automatically bans IPs that trigger too many failed login attempts. It is one of the simplest and most effective tools for reducing brute force noise.
sudo apt install fail2ban
# Create a local config (never edit jail.conf directly - updates overwrite it)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.localEdit /etc/fail2ban/jail.local to configure the SSH jail:
# /etc/fail2ban/jail.local - relevant SSH section:
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600 # ban for 1 hour
findtime = 600 # count failures within 10 min windowsudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Verify it is running
sudo fail2ban-client status sshdStep 5: Enable automatic security updates
A large percentage of real-world server compromises exploit known vulnerabilities that were already patched. Servers that are never updated are sitting on public exploits that any script kiddie can run. Automatic security updates solve this without requiring a manual process.
sudo apt install unattended-upgrades
# Enable it
sudo dpkg-reconfigure --priority=low unattended-upgradesThe default configuration applies security patches automatically. Application updates (which can occasionally break things) are left for manual review. This is the right balance for a production server.
Step 6: Change the default SSH port (optional but useful)
Bots scan port 22 constantly. Moving SSH to a non-standard port does not make you more secure in a meaningful way, but it dramatically reduces log noise and automated attack volume. The legitimate attacker doing a full port scan will find it. But the 90% of automated bots that only check port 22 will walk past.
# /etc/ssh/sshd_config
Port 2222 # or any port above 1024 that is not in use
# Update UFW to match
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp
sudo systemctl restart sshdStep 7: Check for listening services you do not need
Newly provisioned servers sometimes come with services running that you did not explicitly install. Check what is listening:
# Show all listening ports and which processes own them
sudo ss -tlnpAnything listening on 0.0.0.0 is accessible from the internet if it is not blocked by UFW. Look for anything unexpected: databases should be listening on 127.0.0.1 only, not on a public interface.
# If MySQL or PostgreSQL is listening on 0.0.0.0, lock it down:
# In /etc/mysql/mysql.conf.d/mysqld.cnf:
bind-address = 127.0.0.1
# In /etc/postgresql/16/main/postgresql.conf:
listen_addresses = 'localhost'The minimum viable secure VPS
If you do nothing else, do these four things:
- SSH keys only, no password auth
- Root login disabled
- UFW enabled with deny-by-default
- Fail2ban running on SSH
Those four steps reduce the attack surface by the vast majority for a typical VPS. The rest of the list improves the posture further, but start with those four.
$ harden --your-server
If you have a VPS that was set up quickly and you are not sure what state it is in, I can go through it. I do this as part of server setup and infrastructure work.
$ ./get-server-audit.sh →