How to Secure Your Own VPS
The list I always forget
Every time I spin up a new server I do the same handful of things, and every time I have to look up half of them again. So this is me writing them down once. It's less a tutorial and more a checklist I can come back to.
None of this is advanced. It's the boring, load-bearing stuff: don't run as root, turn off password logins, close the ports you're not using. But "basic" and "done" aren't the same thing, and an unhardened box on the public internet gets found fast. Bots scan the whole address space constantly, and a fresh server with password SSH open is a target within minutes.
It doesn't matter much what you're putting on the box. A self-hosted app, a database, a personal agent like OpenClaw or Hermes, a side project you'll forget about. The agent case is the scariest, since something that can run shell commands and read your files is exactly what you don't want an attacker holding, but the baseline is the same for all of it. Do these first, before you install the thing you actually came here to run.
I use Hetzner and Ubuntu, so the commands lean that way, but this maps to any provider and most Debian-based distros.
Patch it before anything else
The first thing on a brand new box, every time, before I install a single package:
apt update && apt upgrade -yAn unpatched system is the easiest way in. The image you booted from is already weeks or months old, so assume there are known holes already public and close them before you do anything else.
Stop working as root
Root can do anything, which is exactly why you don't want to live there. If something you run goes wrong, whether it's a bad script, a bug, or an attacker, you want the damage capped at one user instead of the entire machine.
Make a normal account, give it sudo, and use it from now on:
adduser youruser
usermod -aG sudo youruserThen open a second terminal and confirm it works (ssh youruser@YOUR_SERVER_IP, then sudo whoami should print root) before you trust it. Keep the root session open until you're sure. That second-terminal habit has saved me more than once.
Use SSH keys, kill passwords
This is the one that matters most and the one I'm laziest about. Passwords get guessed, reused, and leaked. Keys don't. Switching to key-only auth quietly ends the entire category of brute-force attacks.
Generate a key on your own machine and copy it up:
ssh-keygen -t ed25519 -C "youruser@my-vps"
ssh-copy-id -i ~/.ssh/id_ed25519.pub youruser@YOUR_SERVER_IPConfirm you can log in with the key before you turn passwords off in the next step. If you use more than one computer, run ssh-copy-id from each one so every machine's key is on the server.
Lock down the SSH config
Once keys work, close the obvious doors in /etc/ssh/sshd_config:
Port 2222 # off the default port the bots hammer
PermitRootLogin no # no root login, ever
PasswordAuthentication no # keys only
PermitEmptyPasswords no
MaxAuthTries 3
AllowUsers youruser # only you get inTest the config with sudo sshd -t (no output means it's fine), then restart with sudo systemctl restart sshd. Here's the part I've burned myself on: do not close your session yet. Open a new terminal and confirm ssh -p 2222 youruser@YOUR_SERVER_IP works while you still have a way back in. If it fails, the old session is your lifeline to fix it.
While you're here, drop an alias in your local ~/.ssh/config so you're not typing the port and user every time:
Host myvps
HostName YOUR_SERVER_IP
User youruser
Port 2222
IdentityFile ~/.ssh/id_ed25519
Now it's just ssh myvps.
Close every port but the ones you need
Your server has tens of thousands of ports and by default they're all reachable. The fix is a default-deny firewall: block everything coming in, then open only what you actually use. Allow SSH before you enable it, or you'll lock yourself out (ask me how I know):
sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2222/tcp comment 'SSH'
sudo ufw enableWhatever you run later, only open its port if it genuinely needs to face the public. For a lot of self-hosted things, especially anything personal, the answer is that it doesn't, and you should reach it over a private network instead (more on that below).
Auto-ban the brute forcers
Even with password login off, attackers keep knocking. Fail2ban watches the logs and bans IPs that keep failing, which keeps the noise down and your logs readable. Install it, then create /etc/fail2ban/jail.local (leave the main config alone):
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10 # don't ban yourself
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 24hEnable it with sudo systemctl enable --now fail2ban. Check who's currently banned any time with sudo fail2ban-client status sshd, and if you ever catch yourself in the net, sudo fail2ban-client set sshd unbanip YOUR_IP lets you out.
Let it patch itself
I will not remember to log in and run updates every week. So I don't rely on remembering. Unattended upgrades keep the OS current on their own, which matters because the window between a vulnerability going public and getting exploited is often hours, not days:
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgradesIf you want it to reboot for kernel updates too, add Unattended-Upgrade::Automatic-Reboot "true"; and a quiet reboot time to /etc/apt/apt.conf.d/50unattended-upgrades. A few seconds of downtime at 4am beats a box that's been running a known-vulnerable kernel for a month.
Hide it behind a private network
This is the step that changes the whole posture, and it's the one I skip when I'm rushing. A tool like Tailscale builds a private, encrypted network between your devices, so your laptop, your phone, and the server all talk over private IPs nobody else can reach. Anything you don't need to serve publicly should live in here.
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo systemctl enable tailscaled # start on boot
tailscale ip -4 # note this private addressNow services can bind to the private interface instead of the public one, and you allow them through the firewall for that interface only:
sudo ufw allow in on tailscale0 to any port 8080 comment 'my app via Tailscale'From the public internet that port simply doesn't exist. Once you trust the setup, you can move SSH onto the private network the same way and drop its public rule entirely, at which point a stranger scanning your IP finds nothing at all. For anything sensitive, and especially for a self-hosted agent, this is the difference between "exposed and hoping" and "genuinely private."
Lock down file permissions
Easy to forget because nothing breaks when you skip it. Your keys, configs, and any credentials the things on your box use should not be readable by other users or stray processes. The essentials:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys ~/.ssh/id_*
chmod 644 ~/.ssh/id_*.pub
chmod 750 /home/youruserSame idea for whatever you install: config files holding tokens or secrets should be 600 and owned by your user, not world-readable. If you run an app that keeps an API key in a dotfile, that's the first place an attacker looks after they're in.
Check on it now and then
Hardening isn't a one-time event, but the upkeep is light. Once in a while I glance at who's been trying to get in and what's actually listening:
sudo grep "Failed password" /var/log/auth.log | tail -20 # who's knocking
sudo fail2ban-client status sshd # who got banned
sudo ss -tulpn # what's listeningThat last one is the most useful. If a port shows up that you don't recognize, something is running that you didn't intend, and that's worth chasing down. If you ever manage to lock yourself out completely, most providers (Hetzner included) give you a web console that logs in with your password so you can undo a bad firewall rule.
The whole thing in one breath
If you skim nothing else, this is the list: patch on boot, make a non-root user, switch to SSH keys and turn off passwords, harden the SSH config, deny-by-default firewall, Fail2ban, automatic updates, put private things behind a private network, and fix your file permissions.
None of it is clever. It's just the stuff that's easy to skip when you're eager to get to the fun part, and easy to regret later. Whether you're standing up a real server or self-hosting an agent that can touch your files, do these first. Future me, this is your reminder.