Hardening
The essentials for a single-box deployment — firewall, SSH keys, automatic security updates. This isn’t a full security audit; it’s the baseline below which you should not operate. Ten minutes, the same day you deploy.
-
Firewall (ufw): only SSH, HTTP, HTTPS.
Terminal window apt-get -y install ufwufw allow OpenSSHufw allow 80/tcpufw allow 443/tcpufw allow 443/udp # HTTP/3ufw --force enableufw status verboseEverything else (Postgres, Gunicorn, …) already listens only on Docker’s internal network — nothing but Caddy publishes ports — so the firewall is a second fence, not the only one.
Hostinger’s hPanel and DigitalOcean’s Cloud Firewalls can enforce the same rules one layer out; if offered, mirror them there too (allow 22, 80, 443).
-
SSH keys instead of passwords.
On your laptop (skip if you already have
~/.ssh/id_ed25519.pub):Terminal window ssh-keygen -t ed25519 -C "you@example.com" # accept the defaultsssh-copy-id root@YOUR_VPS_IP # copies the public key overssh root@YOUR_VPS_IP # must log in WITHOUT a password nowThen, on the server, turn password logins off:
Terminal window sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_configsed -i 's/^#\?KbdInteractiveAuthentication.*/KbdInteractiveAuthentication no/' /etc/ssh/sshd_configsystemctl restart ssh -
Automatic security updates.
Terminal window apt-get -y install unattended-upgradesdpkg-reconfigure -plow unattended-upgrades # choose "Yes"Ubuntu will now install security patches by itself. Kernel updates still need an occasional reboot — a
systemctl rebootduring a quiet hour every month or two is fine; the stack starts itself (restart: unless-stopped+ auto-migrate on boot).
The application side (already done — verify, don’t configure)
Section titled “The application side (already done — verify, don’t configure)”.envis the only secrets file — it never enters the Docker image and is git-ignored. Don’t commit it, don’t email it.- Production settings refuse
DEBUG=True, a weak/defaultDJANGO_SECRET_KEY, and wildcardALLOWED_HOSTSat boot — if you see such an error indocker compose logs web, that’s the guardrail working. - HTTPS is enforced end-to-end (Caddy redirects HTTP; Django sets HSTS).
- Optional but recommended: error reporting — set
ENABLE_SENTRY=TrueandSENTRY_DSN=...in.env(the free Sentry tier is fine), thendocker compose up -d web.
Two minutes of verification
Section titled “Two minutes of verification”ufw status | grep -E "80|443|22" # the three allowed ports# password login must FAIL now (this forces password auth, skipping your key):ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password root@YOUR_VPS_IPcurl -sI http://api.your-domain.com/health/ | head -1 # 308 → HTTPS redirect