Every new VPS ships wide open: password authentication enabled, root login allowed, no firewall, and every service listening to the world by default. Most brea…
Every new VPS ships wide open: password authentication enabled, root login allowed, no firewall, and every service listening to the world by default. Most breaches on small servers aren't sophisticated — they're bots scanning for exactly these defaults. This guide walks through a practical, no-nonsense hardening checklist you can run through on any fresh Ubuntu/Debian server in about half an hour, before you deploy anything on it.
Prerequisites / What You'll Need
- A freshly provisioned Linux server (Ubuntu 22.04/24.04 or Debian 12 assumed here)
- Root access via SSH, or console access from your provider
- An SSH key pair on your local machine (we'll generate one if you don't have it)
- 30 minutes and a terminal
Step 1: Create a Non-Root User
Working as root day-to-day is risky — one typo in a destructive command has no safety net. Create a dedicated user with sudo privileges instead:
adduser deploy
usermod -aG sudo deploy
Test that it works before moving on — open a second terminal session and confirm you can log in and sudo as this user. Don't close your root session until this is confirmed; if something's misconfigured, you want a way back in.
ssh deploy@your-server-ip
sudo whoami # should print "root"
Step 2: Set Up SSH Key Authentication
Password-based SSH login is the single most common entry point for automated attacks. Switching to key-based auth removes that vector entirely.
If you don't already have a key pair on your local machine:
ssh-keygen -t ed25519 -C "your-email@example.com"
Copy your public key to the server:
ssh-copy-id deploy@your-server-ip
Confirm you can log in with the key before disabling passwords:
ssh deploy@your-server-ip
Step 3: Disable Password and Root Login
Once key-based login is confirmed working, edit the SSH daemon config:
sudo nano /etc/ssh/sshd_config
Set (or update) these values:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Restart SSH to apply changes:
sudo systemctl restart ssh
Important: Keep your current session open while you test this. Open a fresh terminal and try logging in with your key before closing anything — if you get locked out, most cloud providers offer a web-based console to fix it, but it's an avoidable headache.
Step 4: Configure a Firewall with UFW
ufw (Uncomplicated Firewall) is a friendly front-end for iptables and is more than sufficient for most servers:
sudo apt update
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
If you're running a web server, allow those ports too:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Check the active rules:
sudo ufw status verbose
The principle here: deny everything by default, then explicitly allow only what you actually need exposed.
Step 5: Install Fail2ban
Even with password auth disabled, your SSH port still gets hammered by bots probing for weak configs. Fail2ban watches auth logs and temporarily bans IPs after repeated failed attempts:
sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban
Create a local override file so your settings survive package updates:
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 4
bantime = 3600
findtime = 600
This bans an IP for an hour after 4 failed attempts within a 10-minute window. Restart to apply:
sudo systemctl restart fail2ban
Check active bans anytime with:
sudo fail2ban-client status sshd
Step 6: Enable Automatic Security Updates
Unpatched software is one of the most common ways servers get compromised, and manually applying updates is easy to forget. Automate the boring but critical patches:
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
This prompts you to enable automatic installation of security updates. It won't auto-reboot by default — for a personal server, that's usually fine; you can check /var/log/unattended-upgrades/ periodically to see what was applied.
Step 7: Reduce Your Attack Surface
Check what's actually listening on the network and shut down anything you don't recognize or need:
sudo ss -tulpn
Anything unfamiliar here is worth investigating — either it's a service you forgot you installed, or it's something that shouldn't be there at all. Disable services you don't need:
sudo systemctl disable --now service-name
Step 8: Set Up Basic Intrusion Awareness
Install logwatch for a simple daily digest of what's happening on your server — auth attempts, cron activity, disk usage warnings:
sudo apt install -y logwatch
sudo logwatch --detail high --mailto you@example.com --range today
It's not a full SIEM, but for a small server it's a lightweight way to notice something unusual before it becomes a real problem.
Common Pitfalls / Troubleshooting
- Locking yourself out of SSH. Always test key-based login and
sudo access in a second session before closing your original root/working session. This single habit prevents 90% of "I'm locked out of my own server" situations.
- UFW blocking a port you actually need. If a service stops being reachable after enabling UFW, check
sudo ufw status first — it's almost always a missing allow rule, not a broken service.
- Fail2ban banning your own IP. If you mistype your password a few times while testing, you can lock yourself out. Unban yourself from the console with
sudo fail2ban-client set sshd unbanip YOUR_IP.
- Forgetting this is an ongoing process. Hardening isn't a one-time checklist — review
ufw status, fail2ban logs, and pending updates periodically, not just on day one.
- Skipping the non-root user step. It's tempting to skip this on a "just for me" server, but a compromised root-only setup with no separation of privilege is much harder to contain if something does go wrong.
Wrapping Up
None of these steps are exotic — key-based SSH, a default-deny firewall, fail2ban, and automatic updates are table stakes for any server exposed to the internet. But it's remarkable how many servers skip them simply because the defaults feel "good enough" at first. Running through this checklist on day one, before deploying anything, means your app's security posture starts from a solid baseline instead of an afterthought.
From here, reasonable next steps include setting up centralized log monitoring if you're managing multiple servers, restricting SSH access to a specific IP range with UFW if your access point is stable, and considering a tool like auditd if you need deeper visibility into system-level changes.
Further Reading