Deploying a Flask Tools Site on a Hardened VPS Behind Caddy

Tested on: Ubuntu 24.04 LTS, Python 3.12.3, Caddy 2.x, gunicorn 23.0, Flask 3.x, SQLite as the data store. The pattern is portable to any small Python web app — drop in whichever Flask / Starlette / FastAPI codebase you have. Why this matters Most Flask deployment tutorials end at flask run --host 0.0.0.0 and a hand-wave about Nginx. That gap — between “the app starts” and “the app is a service my colleagues would trust at 2am” — is where the operationally interesting decisions live: which user runs it, how it talks to the reverse proxy, where its secrets live, how it survives a reboot, how its certificate renews itself. ...

11 min

Hardened RHEL / AlmaLinux VPS Baseline

Tested on: AlmaLinux 9.4 (kernel 5.14.x). The same steps apply to Rocky Linux 9 and Red Hat Enterprise Linux 9.x — all three share the same package set, service names, and SELinux profile. Why this matters A fresh RHEL-family cloud image differs from Ubuntu’s defaults in two important ways: SELinux is in enforcing mode by default — which is good, until an operator hits a denied syscall, panics, and runs setenforce 0. The baseline below assumes you’ll keep SELinux on and learn to tune it. The default firewall is firewalld, not ufw — same idea, different vocabulary. Internal-API hosts often ship with firewalld enabled but permissive; never trust the defaults without inspecting. Otherwise the threat model and the order of operations match the Ubuntu baseline: create a non-root user, harden SSH, enforce a host firewall, enable automatic security updates, turn on auditing, fix time. This guide is the RHEL-family-specific translation. ...

7 min

Hardened Ubuntu VPS Baseline

Tested on: Ubuntu 24.04 LTS (Noble Numbat), kernel 6.8.x, on a Hetzner Cloud CX22 instance. Steps are valid on any cloud VPS image of the same Ubuntu version. Why this matters A fresh Ubuntu cloud image is fine for a tutorial and unsuitable for production. Out of the box: The root account often accepts SSH key auth. No host firewall is enforced (the cloud provider’s network firewall is not a substitute, especially for internal traffic). Security updates apply only if you manually run apt upgrade. Time can drift by minutes, breaking TLS verification and log correlation. Audit logging is not enabled, so post-incident investigation depends on whatever syslog happens to have captured. This baseline closes those gaps in an opinionated order that minimises the window during which the server is reachable but not yet hardened. Before running step 1, pick your processors and decide your logging posture — some of the choices below depend on it. ...

6 min

Pre-launch VPS Security Checklist

Scope Applies to any new Linux server intended to handle production traffic or production data: web application, API backend, WordPress site, internal tool, email server, jump host. Work top-to-bottom — items reference the guides for the “how”; this checklist is the “have I done it” layer. Compliance references map only where they actually apply. They are not padded with vague mappings, and they are not legal advice — for a formal audit, use your certifying body’s control set as the authoritative source. ...

4 min

Privacy by Design for a New Server Build

Applies to: any new Linux server intended for production where you will store, process, or transit personal data — including the metadata kind (IP addresses, user agents, login timestamps), which counts under GDPR whether or not your application has a “users” table. Why this matters GDPR Article 25 — Data protection by design and by default — is the legal text that turns “we should think about privacy” into “you must demonstrate that you thought about privacy, on paper, before you started processing.” It applies whether or not you have a website with a signup form. An Nginx access log with full client IPs is processing personal data. ...

10 min