Securing Your VPS with UFW Firewall Rules and SSH Hardening

Securing Your VPS with UFW Firewall Rules and SSH Hardening

A fresh VPS is like an unlocked door on the internet. Within minutes of going live, you'll see automated SSH brute-force attempts in your logs. I've been running self-hosted applications on budget VPS providers like RackNerd (around $40/year for a public-facing instance) for years, and the first thing I do after provisioning is lock it down: UFW firewall rules and SSH hardening are non-negotiable.

In this tutorial, I'll walk you through configuring UFW (Uncomplicated Firewall) and hardening SSH access so you can sleep at night knowing your VPS isn't a vector for someone else's botnet.

Why UFW and SSH Hardening Matter

By default, a Ubuntu or Debian VPS exposes SSH on port 22 to the entire internet. Port scanners find it within hours. Without SSH hardening and firewall rules, an attacker can attempt millions of password combinations. Even with strong passwords, if you allow password-based authentication, you're relying on that single secret being truly strong.

UFW is Ubuntu/Debian's user-friendly wrapper around iptables. It lets you write human-readable rules instead of cryptic firewall commands. SSH hardening means disabling password login, enforcing key-based authentication, disabling root login, and tuning timeout values.

Together, they create a baseline security posture that costs nothing to implement and blocks the vast majority of automated attacks.

Step 1: Set Up SSH Key-Based Authentication

Before you lock down SSH, ensure you have a way to access your VPS that doesn't rely on passwords. On your local machine, generate an SSH keypair if you don't already have one:

ssh-keygen -t ed25519 -f ~/.ssh/vps_key -N ""

This creates a modern Ed25519 keypair. The public key is in ~/.ssh/vps_key.pub; the private key is in ~/.ssh/vps_key. Copy the public key to your VPS:

ssh-copy-id -i ~/.ssh/vps_key user@your-vps-ip

Replace user with your actual username (often root on fresh VPS instances, but preferably a non-root user). Once copied, test it:

ssh -i ~/.ssh/vps_key user@your-vps-ip

If you're in, you're good. If not, troubleshoot before proceeding—you don't want to lock yourself out.

Step 2: Configure SSH for Key-Only Access

Now SSH into your VPS and edit the SSH daemon configuration:

sudo nano /etc/ssh/sshd_config

Find or add these lines and set them exactly as shown:

PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
Protocol 2
X11Forwarding no
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 2
AuthorizedKeysFile .ssh/authorized_keys

Let me break down what each does:

Save the file (Ctrl+O, Enter, Ctrl+X in nano). Then validate and restart:

sudo sshd -t
sudo systemctl restart ssh

The -t flag tests the config for syntax errors. If it passes, restart is safe.

Watch out: Do not close your current SSH session yet. Open a new terminal and test login with your key. If you can't connect, you can still fix it in your current session before you lose access.

Step 3: Install and Enable UFW

UFW is usually pre-installed on Ubuntu but may not be active. Check:

sudo ufw status

If it says "inactive," enable it. But first, add rules to allow your SSH access (otherwise you'll be locked out):

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw enable

The first two lines set the defaults: deny everything coming in, allow everything going out. The third line allows SSH (port 22 TCP). Then enable the firewall.

Verify it's active:

sudo ufw status numbered

You should see rule 1 allowing port 22.

Step 4: Add Application-Specific Rules

Now add rules for any services you're self-hosting. A few common examples:

# HTTP and HTTPS for web services (Nextcloud, Jellyfin, etc.)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Docker services on custom ports (Immich, Vaultwarden, etc.)
sudo ufw allow 8000/tcp
sudo ufw allow 8080/tcp

# DNS for Pi-hole or AdGuard Home
sudo ufw allow 53/tcp
sudo ufw allow 53/udp

# Wireguard VPN
sudo ufw allow 51820/udp

If you're running Ollama with Open WebUI on port 3000, allow it:

sudo ufw allow 3000/tcp

Check your rules again:

sudo ufw status numbered
Tip: Use UFW's comment feature to label rules: sudo ufw allow 3000/tcp comment 'Open WebUI'. Makes your firewall much easier to audit later.

Step 5: Optional—Change SSH Port (Extra Hardening)

Some operators change SSH to a non-standard port. This won't stop determined attackers, but it does reduce noise in your logs from random port scanners.

Edit /etc/ssh/sshd_config again:

sudo nano /etc/ssh/sshd_config

Find the line Port 22 and change it (I prefer 2222):

Port 2222

Then update UFW and restart SSH:

sudo ufw delete allow 22/tcp
sudo ufw allow 2222/tcp
sudo sshd -t
sudo systemctl restart ssh

Test from a new terminal before closing the current one. Update your SSH config so you don't have to type the port each time:

cat >> ~/.ssh/config << EOF
Host my-vps
    HostName your-vps-ip
    User your-username
    IdentityFile ~/.ssh/vps_key
    Port 2222
EOF

Now you can just ssh my-vps.

Step 6: Test Your Configuration

From your local machine, verify you can still log in:

ssh -i ~/.ssh/vps_key user@your-vps-ip

Or if you changed the port:

ssh -i ~/.ssh/vps_key -p 2222 user@your-vps-ip

Try logging in as root (should fail):

ssh -i ~/.ssh/vps_key root@your-vps-ip

Try with a wrong key (should fail immediately):

ssh -i ~/.ssh/other_key user@your-vps-ip

All three should fail or refuse. Check your firewall is actually blocking unwanted traffic by trying to connect to a closed port from your machine:

nc -zv your-vps-ip 9999

Should time out or be refused.

Step 7: Monitor and Maintain

Even with these hardening steps, your VPS will still receive SSH attempts. Monitor your logs to spot trends:

sudo tail -f /var/log/auth.log | grep "Failed password"

You should see many failed attempts from random IPs—that's normal and harmless now that you've disabled password login. If you see thousands per day, consider installing fail2ban to automatically rate-limit repeat offenders, but that's a topic for another tutorial.

Review your UFW rules occasionally:

sudo ufw status verbose

Remove rules you no longer need. A smaller attack surface is a safer one.

Next Steps

You now have a locked-down VPS ready for self-hosting. With UFW and SSH hardening in place, you can confidently deploy Nextcloud, Jellyfin, Vaultwarden, or any other self-hosted app without losing sleep over security.

From here, I'd recommend:

For a budget VPS on RackNerd or similar providers, this baseline security posture is all you need to run production-grade self-hosted services safely.

Discussion