# Setting up a secure VPS

> One server, two open ports, and no SSH daemon waiting for company. How the box behind this site is locked down.

- date: 2026-08-30
- tags: vps, security, cloudflare

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.

```sh
# 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:

```sh
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.