Hardening a VPS Before Installing AI Agents
Before installing the first AI agent on my VPS, I hardened it: key-only SSH, a firewall, Tailscale, and a safety net in case something goes wrong. Second article in the series on my agentic environment.
By Nicolas Cousin — Published on September 22, 2026
Hardening a VPS Before Installing AI Agents
TL;DR
Before installing a single AI agent on my VPS, I hardened it: system updates, key-only SSH, a UFW firewall set to deny-everything-except-what's- needed, a Tailscale VPN so SSH is no longer publicly exposed, and a safety net (a recovery key, a KVM console) in case I lock myself out. An agent capable of running commands raises the stakes of hardening considerably: it's no longer just me who can get something wrong.
Table of contents
- Why harden before installing anything
- Updating the system
- SSH by key, nothing else
- The firewall: deny by default, allow what's needed
- The safety net
- Tailscale: closing SSH to the public
Why harden before installing anything
Order matters. I hardened the VPS before installing Docker, Ollama, or any agent on it, not after. An AI agent that can run shell commands isn't just a tool you use anymore: it's a process with potentially the same rights I have on the machine. A bad network configuration or a root account reachable by password is already risky with a single human at the controls. With an agent capable of acting on its own, the margin for error shrinks further.
None of what follows is specific to AI: it's the standard basics of hardening an Ubuntu server exposed to the internet. I'm spelling it out because it becomes, I think, a prerequisite you can no longer skip once autonomous agents enter the picture.
Updating the system
The first command, before anything else:
sudo apt update && sudo apt upgrade -y
Nothing sophisticated, but an up-to-date system closes a good chunk of known vulnerabilities before you even touch the configuration.
SSH by key, nothing else
Two settings in /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
The order in which you apply this matters, and it's the easiest mistake to make: disabling password authentication before you've copied your own public key to the server, and locking yourself out. So I did it in this exact order:
- Generate a key pair locally if you don't already have one.
- Copy the public key to the VPS:
ssh-copy-id ubuntu@my-vps - Reconnect once with the key to confirm it works, before touching anything else.
- Only then, edit
sshd_configand restart the service:sudo systemctl restart ssh
If step 3 is skipped and key-based login doesn't work, you end up with a VPS reachable only through the host's emergency console (see below) until it's fixed.
The firewall: deny by default, allow what's needed
UFW set to "deny everything incoming, allow everything outgoing":
sudo ufw default deny incoming
sudo ufw default allow outgoing
Another classic trap here: enabling UFW before explicitly allowing the SSH port cuts access on the next reconnect, not necessarily the current session, but better not to count on that. The SSH rule needs to be added before enabling, not after:
sudo ufw allow OpenSSH
sudo ufw enable
At this point the VPS only accepts incoming SSH connections, and lets everything else out freely (updates, API calls, and so on).
The safety net
Before touching anything else, a question worth asking: what happens if the next firewall rule locks me out? Closing public access later in this article means giving myself the means to get badly wrong if a configuration goes sideways. Two safety nets, checked beforehand, not after:
- a recovery SSH key, generated separately and stored in a secure password manager rather than on the machine used day to day, protected by a passphrase like any other private key;
- the host's KVM console (OVH in my case), which gives direct access to the machine independent of the network, including if both SSH and Tailscale become unreachable.
I thankfully haven't needed either of them so far, but having them checked and ready before closing anything changes the nature of the risk: a misconfiguration becomes recoverable in a few minutes instead of a back-and-forth with the host's support.
Tailscale: closing SSH to the public
With the safety net checked, the next step is to stop exposing SSH on the public IP at all. I use Tailscale, a VPN that creates a private network between my machines (the "tailnet"):
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
Once connected to the tailnet and the SSH connection over the Tailscale IP verified, I close public access:
sudo ufw allow in on tailscale0 to any port 22
sudo ufw delete allow OpenSSH
Port 22 is no longer reachable from the internet at all, only from the private Tailscale network. An external port scan of the VPS no longer sees anything open for SSH.
The VPS is now reachable only by key, only over Tailscale, with a verified safety net. Next: installing Docker, Ollama, and Hermes on this base, and the first trap that wasn't on the original plan.