Hardening a Circus Server
The initial setup deliberately grants broad access — sudo, password auth — to keep configuration simple. Once the server is configured and the first deploy is verified, harden it. Do the steps in order: if you disable password auth before confirming key auth works, you can lock yourself out and need console access to recover.
Lock down SSH
- Verify key auth first.
ssh -i circus_deploy_key deploy@YOUR_IP "echo ok"must succeed before you change anything. - Disable root login — set
PermitRootLogin noinsshd_configand restartsshd; confirmssh root@YOUR_IPis rejected. - Disable password authentication — set
PasswordAuthentication noand restart; confirm a password-only attempt is rejected. - (Optional) remove sudo from
deploy— CI needs only Docker access. Removing sudo tightens the blast radius, but then server maintenance needs a separate admin user or console. Skip this if you're the only operator and rely on sudo for upkeep.
Firewall
Default-deny inbound and open only what Caddy and SSH need:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (Caddy redirect + ACME)
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
Note that Postgres (5432) is deliberately not opened — the container publishes no host port at all, so it's reachable only over the Docker network or via docker compose exec on the box itself.
Supply-chain protection
The platform enforces a 30-day npm package-age policy via .npmrc (min-release-age=30) — no dependency published in the last 30 days can be installed, blunting supply-chain attacks that rely on rapid uptake of a malicious release. If you develop plugins and add dependencies, also consider installing Socket on the repo; it flags suspicious package changes (typosquatting, install scripts, obfuscated code) on PRs.
Known limitations (accepted tradeoffs)
This single-host, demo-oriented model accepts some risks — worth documenting, worth revisiting before serving real clients:
- Secrets sit in plain text in
.env/n8n.envon disk (mitigation: key-only SSH + firewall; for real production, a secrets manager). GHCR_TOKENpasses through the SSH session duringdocker login(mitigation: rotate periodically).- Postgres trusts local connections — any process on the box can
execinto it without a password (mitigation: restricted server access). - Staging and production share one host — acceptable because production here is a demo, not a client system; use separate servers for real production.