wihan/dev

writing / post

Setting up a secure VPS

2026-08-30vps · security · cloudflare2 minmarkdown

The server behind this site is one cheap VPS. The security model is short enough to fit in a paragraph, which is the point: a model you can hold in your head is one you will actually maintain.

First boot, then close the door

The first session is the usual ritual: update everything, create a non-root user, put a key in authorized_keys, turn off password authentication. The key never touches disk on my side; the 1Password agent holds it and answers the socket.

# sshd_config, the lines that matter
PasswordAuthentication no
PermitRootLogin no

Then the part most guides skip: once Dokploy is installed and reachable, the firewall closes everything that is not doing a job. What remains is 80 and 443 for Traefik, and one UDP port for the Factorio server. Port 22 does not answer from the internet. A port that is filtered cannot present a login prompt, and a login prompt that does not exist cannot be brute-forced.

Managing a box you cannot shell into

This sounds inconvenient until you notice what replaces it. Dokploy exposes an API behind HTTPS, scoped by an API key. Deploys arrive through a GitHub webhook. Logs, container files, and even files inside Docker volumes come out of API endpoints, so the day-2 operations that used to justify SSH are covered:

curl -s "$DOKPLOY/api/compose.readLogs" -H "x-api-key: $KEY" ...
curl -s "$DOKPLOY/api/dockerVolume.readVolumeFile" -H "x-api-key: $KEY" ...

The API key lives in 1Password and rotates from the dashboard. There is no agent forwarding, no known_hosts drift, and no temptation to “quickly fix it on the server” - the kind of one-off manual change that piles up until nobody can rebuild the machine.

The proxy caveat

Web traffic rides the Cloudflare proxy, so the DNS records for the sites point at the edge and not at the machine. That hides the origin IP from casual scans, but it is a curtain, not a wall: the Factorio record must be DNS-only because UDP does not ride the proxy, and that record names the real address. The firewall is the actual boundary. Treat the proxy as a bonus and configure the box as if the IP were public, because it is.