How to Harden Your Linux VPS in 10 Minutes
The moment you spin up a fresh Linux VPS, the clock starts ticking. Within hours — sometimes minutes — your IP shows up in scanner logs and bots begin trying default credentials, common SSH usernames, and known web exploits. I've watched a brand-new server log over four thousand brute-force SSH attempts in its first 24 hours of life.
Most of those attacks are stoppable in 10 minutes of work. Here's the no-fluff checklist I run on every new VPS — the same one I used when I built byte-guard.net itself.
What You'll Need
- A fresh VPS running Ubuntu 22.04+ or Debian 11+ (most steps work on any modern distro)
- Root SSH access — ideally a just-provisioned server, before you've done anything else
- 10 minutes
- An SSH key on your local machine (we'll generate one if you don't have it)
Note: these commands assumeapt-based distros. If you're on Rocky, Alma, or RHEL, swapaptfordnfandufwforfirewalld— the principles are identical.
Step 1 — Update Everything
Bots love unpatched systems. The first thing to do on any new server is apply outstanding updates:
apt update && apt upgrade -y
This pulls down the package index and installs every available update. On a fresh VPS this typically takes 1-2 minutes.
Step 2 — Create a Non-Root User
You should never SSH in as root for daily work. If your root account gets compromised, you've handed an attacker complete control. A regular user with sudo access gives you the same power but keeps an audit trail and adds a small barrier between mistakes and disaster.
adduser amine
usermod -aG sudo amine
Replace amine with your username. adduser will prompt you for a password — make it strong (a passphrase from pwgen -s 32 1 is excellent), but you'll mostly be using SSH keys after the next step.
Step 3 — Set Up SSH Key Authentication
Passwords get brute-forced. Ed25519 SSH keys don't, in any practical sense. If you don't have one yet, generate it on your local machine, not the server:
ssh-keygen -t ed25519 -C "your_email@example.com"
Why ed25519 over rsa? It's faster, smaller, and more modern. The default rsa 3072-bit key is also fine, but ed25519 is the current best practice.
Then copy it to the server, replacing the placeholder with your user and IP:
ssh-copy-id amine@your-server-ip
Now test it from a new terminal:
ssh amine@your-server-ip
You should log in without being asked for a password. If that works, you're ready to lock down SSH itself.
Step 4 — Lock Down SSH
This is the single biggest security win. Open the SSH server config:
sudo vim /etc/ssh/sshd_config
Find and change these lines (uncomment them if needed):
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM no
What each does:
PermitRootLogin no— root cannot SSH in at allPasswordAuthentication no— only SSH keys work, no passwordsPubkeyAuthentication yes— explicitly enable SSH keys (usually default but be explicit)ChallengeResponseAuthentication noandUsePAM no— close fallback authentication paths
⚠️ Don't close your current session yet. Test that you can log in via key from a new terminal first. If you've made a config mistake, you'll need that working session to fix it.
Save and reload SSH:
sudo systemctl reload sshd
Open a brand new terminal and SSH in as your user. If it works, your server is now key-only. Now you can safely close the old root session.
Step 5 — Set Up UFW (the Firewall)
ufw is Ubuntu's user-friendly firewall. It ships with most modern distros and just needs to be enabled with a sensible default policy:
sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
Verify the rules:
sudo ufw status verbose
You should see only port 22 (SSH) open. If you're running a web server, also allow:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Don't allow ports you're not actually using. Every open port is a potential attack surface.
Step 6 — Install fail2ban
Even with key-only SSH, your logs will fill up with rejected brute-force attempts. fail2ban watches the auth log and bans IPs that repeatedly fail to authenticate:
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
Out of the box, the default config protects SSH. Check that the SSH jail is active:
sudo fail2ban-client status sshd
You should see something like:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
To tighten the defaults (out of the box: 5 attempts, 10-minute ban), create a local override:
sudo vim /etc/fail2ban/jail.local
Add:
[sshd]
enabled = true
maxretry = 3
findtime = 10m
bantime = 1h
Then reload:
sudo systemctl restart fail2ban
Three failed attempts in 10 minutes now earns a one-hour ban. Aggressive enough to deter bots, lenient enough that you can recover from your own typos.
Step 7 — Enable Unattended Upgrades
Security patches matter most when they actually get installed. Unattended upgrades automatically apply security updates so you don't have to remember to log in and apt upgrade:
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Choose Yes when prompted. This installs a systemd timer that runs daily and applies security updates only — not feature upgrades, so you won't get surprise breaking changes.
Verify it's running:
sudo systemctl status unattended-upgrades
You should see active (running).
Step 8 — Sanity Check
Run these to verify everything is in place:
# SSH config — both should say "no"
sudo sshd -T | grep -E "permitrootlogin|passwordauthentication"
# Firewall — should show only the ports you opened
sudo ufw status
# fail2ban — should show the sshd jail as active
sudo fail2ban-client status sshd
# Unattended upgrades — should be active
sudo systemctl is-active unattended-upgrades
If everything checks out, your VPS is hardened against the most common automated attacks.
Bonus — Change the SSH Port (Optional)
Moving SSH off port 22 doesn't add real security (it's security through obscurity), but it does massively cut log noise from drive-by scanners. Edit /etc/ssh/sshd_config:
Port 2222
Then update UFW:
sudo ufw delete allow OpenSSH
sudo ufw allow 2222/tcp
sudo systemctl reload sshd
Connect with ssh -p 2222 amine@your-server-ip. Add it to your ~/.ssh/config so you never type the port again:
Host my-vps
HostName your-server-ip
User amine
Port 2222
IdentityFile ~/.ssh/id_ed25519
Now you can just type ssh my-vps.
What This Does NOT Cover
10 minutes gets you the essentials. It does not cover:
- Application-layer security — if you're running a web app, you still need to harden Nginx, your reverse proxy, your CMS, and so on
- Intrusion detection — tools like AIDE or Wazuh for filesystem integrity and behavioral monitoring
- Centralized logging — shipping logs to a separate server so an attacker who lands on the box can't quietly cover their tracks
- Backups — hardening means nothing if you can't restore after an incident
I'll cover those in future posts. For now, you've blocked the overwhelming majority of automated attacks that hit any new VPS.
What's Next
If you're spinning up a VPS for self-hosting (and you should — it's the best way to actually learn how things work), check out the full byte-guard.net build: How I Built byte-guard.net from Scratch on a Hetzner VPS. It uses every step in this post and adds Docker, a reverse proxy, and monitoring on top.
Need a VPS to practice on? I run everything on Hetzner — solid hardware, great pricing, and a clean control panel. [affiliate link coming soon]
Quick recap — the 10-minute checklist:
- ✅
apt update && apt upgrade - ✅ Create non-root user with sudo
- ✅ SSH key auth set up
- ✅ Root login + password auth disabled in
sshd_config - ✅ UFW firewall enabled, only the ports you need
- ✅ fail2ban watching the SSH jail
- ✅ Unattended security updates running
Run this on every new server you build. After a few times you'll be doing it in closer to 5 minutes than 10.