Tools: How to Harden Your Linux VPS in 10 Minutes

Tools: How to Harden Your Linux VPS in 10 Minutes

What You'll Need

Step 1 — Update Everything

Step 2 — Create a Non-Root User

Step 3 — Set Up SSH Key Authentication

Step 4 — Lock Down SSH

Step 5 — Set Up UFW (the Firewall)

Step 6 — Install fail2ban

Step 7 — Enable Unattended Upgrades

Step 8 — Sanity Check

Bonus — Change the SSH Port (Optional)

What This Does NOT Cover

What's Next 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. Note: these commands assume apt-based distros. If you're on Rocky, Alma, or RHEL, swap apt for dnf and ufw for firewalld — the principles are identical. Bots love unpatched systems. The first thing to do on any new server is apply outstanding updates: This pulls down the package index and installs every available update. On a fresh VPS this typically takes 1-2 minutes. 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. 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. 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: 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: Now test it from a new terminal: You should log in without being asked for a password. If that works, you're ready to lock down SSH itself. This is the single biggest security win. Open the SSH server config: Find and change these lines (uncomment them if needed): 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. 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. ufw is Ubuntu's user-friendly firewall. It ships with most modern distros and just needs to be enabled with a sensible default policy: You should see only port 22 (SSH) open. If you're running a web server, also allow: Don't allow ports you're not actually using. Every open port is a potential attack surface. 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: Out of the box, the default config protects SSH. Check that the SSH jail is active: You should see something like: To tighten the defaults (out of the box: 5 attempts, 10-minute ban), create a local override: 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. 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: 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. You should see active (running). Run these to verify everything is in place: If everything checks out, your VPS is hardened against the most common automated attacks. 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: Connect with ssh -p 2222 amine@your-server-ip. Add it to your ~/.ssh/config so you never type the port again: Now you can just type ssh my-vps. 10 minutes gets you the essentials. It does not cover: I'll cover those in future posts. For now, you've blocked the overwhelming majority of automated attacks that hit any new VPS. If you're spinning up a VPS for self-hosting, check out the full 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. I also wrote a deep dive on Docker Security Best Practices — the container-level companion to this guide. Quick recap — the 10-minute checklist: Run this on every new server you build. After a few times you'll be doing it in closer to 5 minutes than 10. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y -weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y -weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade -y adduser amine usermod -aG -weight: 600;">sudo amine adduser amine usermod -aG -weight: 600;">sudo amine adduser amine usermod -aG -weight: 600;">sudo amine ssh-keygen -t ed25519 -C "[email protected]" ssh-keygen -t ed25519 -C "[email protected]" ssh-keygen -t ed25519 -C "[email protected]" ssh-copy-id amine@your-server-ip ssh-copy-id amine@your-server-ip ssh-copy-id amine@your-server-ip ssh amine@your-server-ip ssh amine@your-server-ip ssh amine@your-server-ip -weight: 600;">sudo vim /etc/ssh/sshd_config -weight: 600;">sudo vim /etc/ssh/sshd_config -weight: 600;">sudo vim /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes ChallengeResponseAuthentication no UsePAM no PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes ChallengeResponseAuthentication no UsePAM no PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes ChallengeResponseAuthentication no UsePAM no -weight: 600;">sudo -weight: 500;">systemctl reload sshd -weight: 600;">sudo -weight: 500;">systemctl reload sshd -weight: 600;">sudo -weight: 500;">systemctl reload sshd -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install ufw -y -weight: 600;">sudo ufw default deny incoming -weight: 600;">sudo ufw default allow outgoing -weight: 600;">sudo ufw allow OpenSSH -weight: 600;">sudo ufw -weight: 500;">enable -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install ufw -y -weight: 600;">sudo ufw default deny incoming -weight: 600;">sudo ufw default allow outgoing -weight: 600;">sudo ufw allow OpenSSH -weight: 600;">sudo ufw -weight: 500;">enable -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install ufw -y -weight: 600;">sudo ufw default deny incoming -weight: 600;">sudo ufw default allow outgoing -weight: 600;">sudo ufw allow OpenSSH -weight: 600;">sudo ufw -weight: 500;">enable -weight: 600;">sudo ufw -weight: 500;">status verbose -weight: 600;">sudo ufw -weight: 500;">status verbose -weight: 600;">sudo ufw -weight: 500;">status verbose -weight: 600;">sudo ufw allow 80/tcp -weight: 600;">sudo ufw allow 443/tcp -weight: 600;">sudo ufw allow 80/tcp -weight: 600;">sudo ufw allow 443/tcp -weight: 600;">sudo ufw allow 80/tcp -weight: 600;">sudo ufw allow 443/tcp -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install fail2ban -y -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now fail2ban -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install fail2ban -y -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now fail2ban -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install fail2ban -y -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">enable --now fail2ban -weight: 600;">sudo fail2ban-client -weight: 500;">status sshd -weight: 600;">sudo fail2ban-client -weight: 500;">status sshd -weight: 600;">sudo fail2ban-client -weight: 500;">status sshd 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: 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: 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: -weight: 600;">sudo vim /etc/fail2ban/jail.local -weight: 600;">sudo vim /etc/fail2ban/jail.local -weight: 600;">sudo vim /etc/fail2ban/jail.local [sshd] enabled = true maxretry = 3 findtime = 10m bantime = 1h [sshd] enabled = true maxretry = 3 findtime = 10m bantime = 1h [sshd] enabled = true maxretry = 3 findtime = 10m bantime = 1h -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart fail2ban -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart fail2ban -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart fail2ban -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install unattended-upgrades -y -weight: 600;">sudo dpkg-reconfigure --priority=low unattended-upgrades -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install unattended-upgrades -y -weight: 600;">sudo dpkg-reconfigure --priority=low unattended-upgrades -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install unattended-upgrades -y -weight: 600;">sudo dpkg-reconfigure --priority=low unattended-upgrades -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status unattended-upgrades -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status unattended-upgrades -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">status unattended-upgrades # SSH config — both should say "no" -weight: 600;">sudo sshd -T | grep -E "permitrootlogin|passwordauthentication" # Firewall — should show only the ports you opened -weight: 600;">sudo ufw -weight: 500;">status # fail2ban — should show the sshd jail as active -weight: 600;">sudo fail2ban-client -weight: 500;">status sshd # Unattended upgrades — should be active -weight: 600;">sudo -weight: 500;">systemctl is-active unattended-upgrades # SSH config — both should say "no" -weight: 600;">sudo sshd -T | grep -E "permitrootlogin|passwordauthentication" # Firewall — should show only the ports you opened -weight: 600;">sudo ufw -weight: 500;">status # fail2ban — should show the sshd jail as active -weight: 600;">sudo fail2ban-client -weight: 500;">status sshd # Unattended upgrades — should be active -weight: 600;">sudo -weight: 500;">systemctl is-active unattended-upgrades # SSH config — both should say "no" -weight: 600;">sudo sshd -T | grep -E "permitrootlogin|passwordauthentication" # Firewall — should show only the ports you opened -weight: 600;">sudo ufw -weight: 500;">status # fail2ban — should show the sshd jail as active -weight: 600;">sudo fail2ban-client -weight: 500;">status sshd # Unattended upgrades — should be active -weight: 600;">sudo -weight: 500;">systemctl is-active unattended-upgrades -weight: 600;">sudo ufw delete allow OpenSSH -weight: 600;">sudo ufw allow 2222/tcp -weight: 600;">sudo -weight: 500;">systemctl reload sshd -weight: 600;">sudo ufw delete allow OpenSSH -weight: 600;">sudo ufw allow 2222/tcp -weight: 600;">sudo -weight: 500;">systemctl reload sshd -weight: 600;">sudo ufw delete allow OpenSSH -weight: 600;">sudo ufw allow 2222/tcp -weight: 600;">sudo -weight: 500;">systemctl reload sshd Host my-vps HostName your-server-ip User amine Port 2222 IdentityFile ~/.ssh/id_ed25519 Host my-vps HostName your-server-ip User amine Port 2222 IdentityFile ~/.ssh/id_ed25519 Host my-vps HostName your-server-ip User amine Port 2222 IdentityFile ~/.ssh/id_ed25519 - 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 - An SSH key on your local machine (we'll generate one if you don't have it) - PermitRootLogin no — root cannot SSH in at all - PasswordAuthentication no — only SSH keys work, no passwords - PubkeyAuthentication yes — explicitly -weight: 500;">enable SSH keys (usually default but be explicit) - ChallengeResponseAuthentication no and UsePAM no — close fallback authentication paths - 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 - -weight: 500;">apt -weight: 500;">update && -weight: 500;">apt -weight: 500;">upgrade - Create non-root user with -weight: 600;">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