The First 30 Minutes: VPS Hardening Checklist
A fresh VPS gets its first SSH probe within minutes of getting an IP. Here is the exact sequence I run on every new box — copy-paste ready for Debian 13, with a checklist table at the end.
Assumptions: a fresh Debian 13 image from your VPSRento deploy, you are logged in as root over SSH, and the example IP is 185.220.101.14 — substitute yours. Total time: about half an hour. Do the steps in order; step 2 can lock you out if you rush it.
1. Create your sudo user
Working as root is how typos become incidents. Make a real user, give it sudo, and do everything from there:
# create the user (you'll set a password — still useful for sudo)
adduser ops
# grant sudo
usermod -aG sudo ops
Everything below this point runs as ops unless stated otherwise.
2. SSH keys only — no passwords, no root login
Password authentication is the single biggest hole on an internet-facing box. Bots hammer port 22 with credential lists all day, every day. Keys make the entire attack class irrelevant. On your local machine:
# generate a keypair if you don't have one (ed25519, no passphrase tradeoff is yours)
ssh-keygen -t ed25519 -C "ops@atlas"
# push the public key to the server
ssh-copy-id [email protected]
Verify you can log in with the key in a new terminal — ssh [email protected] — before touching the config. Keep the existing root session open as your escape hatch. Then edit /etc/ssh/sshd_config:
# /etc/ssh/sshd_config — the three lines that matter
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
# validate the config, then reload (Debian 13 uses ssh.service)
sshd -t && systemctl reload ssh
prohibit-password rather than a flat no for root on purpose: root can still authenticate with a key from your workstation, which is occasionally useful for provider-console recovery — but no password will ever work, for anyone, from anywhere. Confirm with sshd -T | grep -E 'passwordauthentication|permitrootlogin'.
3. Firewall: default deny, allow only what you run
Debian's ufw is a thin, sane layer over nftables. The policy is the security; the rules are just exceptions to it:
apt update && apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
# SSH stays open — add this BEFORE enabling
ufw allow 22/tcp comment 'SSH'
# whatever your box actually serves, e.g.:
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw enable
ufw status verbose
Every port you open should have a name and a reason. If you can't name what a port is for, it doesn't get a rule.
4. fail2ban: slow the bots to a crawl
With password auth disabled, brute force is already dead — but fail2ban keeps your logs readable and bans the scanners that poke at everything else:
apt install -y fail2ban
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
maxretry = 5
findtime = 10m
bantime = 1h
systemctl enable --now fail2ban
# watch it work
fail2ban-client status sshd
5. Automatic security updates
The unpatched CVE is a bigger risk than the reboot. Debian's unattended-upgrades installs security updates on its own:
apt install -y unattended-upgrades
# answer Yes to auto-install updates
dpkg-reconfigure -plow unattended-upgrades
The default config in /etc/apt/apt.conf.d/50unattended-upgrades pulls from the security suite only — which is what you want on a server: security fixes automatically, everything else when you choose.
6. Turn off what you don't run
Every running service is attack surface you have to patch. See what's actually listening:
# who is listening, on what, as which process
ss -tulpn
# everything enabled at boot
systemctl list-unit-files --state=enabled
# example: you don't run a print server — nobody does
systemctl disable --now cups
The rule: if you can't say what a listener does, find out before you let it live. On a minimal VPSRento image there should be very little beyond sshd and your own application.
7. Optional but recommended: a WireGuard management plane
The strongest version of SSH security is SSH that doesn't face the internet at all. Run WireGuard on the box, allow SSH only inside the tunnel, and port 22 disappears from every scanner on earth:
apt install -y wireguard
# /etc/wireguard/wg0.conf — minimal management tunnel
# [Interface] Address = 10.8.0.1/24, ListenPort = 51820
# open the tunnel, not the shell
ufw allow 51820/udp comment 'WireGuard'
ufw allow in on wg0 to any port 22 proto tcp comment 'SSH via WG'
ufw delete allow 22/tcp
systemctl enable --now wg-quick@wg0
After that you ssh [email protected] through the tunnel, and the public IP answers on exactly one UDP port. The full WireGuard walkthrough — keys, peers, client configs, phone QR codes — is in the companion guide.
The 10-point checklist
Run down this table after every new deploy, and again after every service you add:
| # | Item | Where it lives | Verify with |
|---|---|---|---|
| 1 | Non-root sudo user | /etc/passwd | id ops |
| 2 | Key-only SSH auth | sshd_config | sshd -T | grep passwordauth |
| 3 | Root password login disabled | sshd_config | sshd -T | grep permitrootlogin |
| 4 | Firewall default-deny incoming | ufw | ufw status verbose |
| 5 | Only named ports open | ufw | ufw status numbered |
| 6 | fail2ban sshd jail active | jail.local | fail2ban-client status sshd |
| 7 | Security updates automatic | apt | systemctl status unattended-upgrades |
| 8 | No unexplained listeners | — | ss -tulpn |
| 9 | Management over WireGuard (optional) | wg0.conf | wg show |
| 10 | Snapshot after hardening | Client area add-on, $1.99/mo | Services → your VPS → snapshots |
Item 10 deserves one sentence: harden first, snapshot second, in that order — a snapshot of a clean, hardened box is your rollback for every future mistake. Daily offsite snapshots are a $1.99/mo add-on in the client area. Thirty minutes of prevention, one click of insurance.