Hardening a fresh VPS: the baseline every server should have
sshd, firewall, unattended upgrades, sysctl, and intrusion prevention — the baseline hardening every internet-facing box needs, and why lockout-safety matters most.
A fresh VPS is a fresh liability. The moment it gets a public IP, automated scanners are knocking — not maybe, not eventually, within minutes. Hardening isn't paranoia; it's the cheap, boring work you do once so the box stops being a soft target. Here's the baseline every internet-facing server should have, and why the most important rule isn't "be secure" — it's "don't lock yourself out."
Start with lockout-safety, not strength
Most hardening guides open with sshd config and firewall rules. That's backwards. The single most common way people break a fresh server is not getting hacked — it's locking themselves out with their own hardening.
So before you change a single setting, set up a fallback path that does not depend on the thing you're about to modify:
- Keep a second SSH session open while you edit
sshd_config. If your changes break login, the open session lets you revert. Never close your last working session until you've confirmed a new one works. - Know your provider's out-of-band console (KVM / VNC / "recovery mode" / serial). When SSH is gone, this is your only way in. Test that you can reach it before you need it.
- Have your SSH public key already installed and tested before you disable password auth. Confirm key login works, then turn passwords off — not the other way around.
The discipline is simple: every hardening step gets a "what's my way back in if this breaks" answer first. A server you can't reach is its own kind of outage, and it usually happens at the worst time.
This is also why we think about hardening and recovery as one motion. A box you can back up, clone, and relocate is a box you can afford to experiment on — if hardening goes sideways, you restore the image and try again instead of rebuilding from memory.
Lock down sshd
SSH is the front door. Tighten it, but tighten it in an order you can survive.
Edit /etc/ssh/sshd_config (or, cleaner, drop a file in /etc/ssh/sshd_config.d/). The settings that earn their keep:
PasswordAuthentication no— kills the entire category of password-guessing attacks. Only after key login is confirmed.PermitRootLogin no— log in as an unprivileged user, escalate withsudo. Root over SSH is a single credential away from total compromise.PubkeyAuthentication yes— keys, not passwords.KbdInteractiveAuthentication no— close the keyboard-interactive side door so password prompts can't sneak back in.
Validate before you reload, every time:
sudo sshd -t && sudo systemctl reload sshd
sshd -t parses the config and refuses to reload on a syntax error — that one habit prevents a huge share of self-inflicted lockouts. Note what we're not recommending as security: changing the SSH port. Moving off 22 cuts log noise from dumb scanners, but it's obscurity, not defense. Do it if you like quieter logs; don't mistake it for hardening.
Put a firewall in front of everything
Default-deny inbound is the whole game. The box should accept connections only on the ports you actually serve, and drop the rest silently.
On most Linux hosts ufw is the least-error-prone front end to nftables:
- Allow SSH first (this is a lockout trap — enable the firewall after the SSH rule exists).
- Allow 80/443 if you're serving web traffic.
- Deny everything else inbound by default; leave outbound open unless you have a reason to restrict it.
If your provider also offers a network-level firewall (a control-plane firewall that sits in front of the VM), use both. The host firewall protects the box; the provider firewall protects you even if the host firewall is misconfigured or the box is mid-reboot. Defense in depth means a single mistake isn't fatal.
One caution worth repeating: a firewall is exactly the tool people use to lock themselves out. Add the allow-SSH rule, confirm a fresh session connects, then enable enforcement.
Turn on unattended upgrades
The vulnerabilities that actually get servers compromised are rarely exotic. They're known CVEs in packages that had a patch available for weeks. The fix is automation, because manual patching doesn't happen on a box you forgot you owned.
On Debian/Ubuntu, unattended-upgrades applies security updates on its own. Configure it to:
- Install security updates automatically.
- Auto-reboot during a low-traffic window if a reboot is required (kernel and libc updates need one to fully apply).
- Email or log the results so you know what changed.
The reboot part matters more than people admit. An unpatched-but-downloaded kernel update protects nothing. If an unattended reboot scares you, that's usually a signal your service isn't resilient to restarts yet — which is its own thing worth fixing. A server that can reboot cleanly at 4am is a server you can patch without babysitting.
Set sane sysctl defaults
The kernel ships with conservative-but-not-hardened network defaults. A few sysctl tweaks raise the floor without breaking normal workloads. Drop them in /etc/sysctl.d/ so they survive reboots:
- Ignore ICMP redirects and don't accept source-routed packets — both are classic ways to get traffic redirected through an attacker.
- Enable reverse-path filtering (
rp_filter) to drop spoofed source addresses. - Log martians so you can see spoofed/impossible packets hitting the box.
- Enable SYN cookies to ride out basic SYN-flood attempts.
- Disable IPv6 router advertisements if you're not using them, so the box doesn't accept routing hints you never asked for.
None of these are silver bullets. They're the equivalent of locking the windows after you've locked the door — cheap, low-risk, and they close off whole classes of trivial network tricks. Apply with sudo sysctl --system and confirm the values took.
Add intrusion prevention and a non-root user
Two final pieces round out the baseline.
Fail2ban (or equivalent)
Even with passwords disabled, you'll see relentless SSH connection attempts. fail2ban watches your logs and temporarily bans IPs that misbehave — failed auth bursts, malformed requests, repeated probes. It won't stop a targeted attacker, but it dramatically cuts log noise and shuts down the dumb, high-volume background traffic that makes real incidents harder to spot. Tune the ban thresholds so a fat-fingered login from you doesn't ban your own office IP.
A real user account
Do your day-to-day work as a non-root user with sudo, with your SSH key on that account. Root should be a place you escalate to, deliberately, not the identity you live in. It limits blast radius: a compromised session or a careless command does less damage when it isn't already root.
The baseline is a starting line
Run through it and you've closed the doors that get fresh servers owned: key-only SSH, default-deny firewall, automatic security patching, hardened kernel defaults, intrusion prevention, no daily root. That's the floor, not the ceiling — audit logging, file-integrity monitoring, and least-privilege service accounts come next.
But the thread running through all of it is the boring one: keep a way back in. Hardening is most dangerous in the moment you apply it, because every lock you add is a lock you can accidentally turn on yourself. Second session open, console access tested, key confirmed before passwords die, allow-rule in place before enforcement — that's what separates a hardened box from a brick. Build the habit once, and the rest of the baseline becomes routine: something you do to every server, calmly, before it ever takes traffic.