Hetzner + Cloudflare hosting stack
Keen Digital runs two hosting environments on purpose, and they never touch: client sites live on Cloudways (Vultr HF, Sydney), and everything internal — demo sites, spec builds, n8n — lives on a single self-managed Hetzner VPS. If one environment has a bad day, it can't take the other one down with it.
The Hetzner box is the interesting half, because "self-managed" usually means "someone's job now." Here's how it's kept from becoming that.
The box
A Hetzner Cloud CX23 (2 vCPU, 4GB RAM, 40GB disk) in Nuremberg, running Ubuntu 24.04 LTS with Nginx, PHP 8.3-FPM, MariaDB, and Docker for n8n. Cheap, full root access, and more than enough for demo sites and automation workloads that don't need to justify managed-hosting fees.
Hardening, once
Before anything gets installed, the server gets locked down in a fixed order: SSH key added in the Hetzner console before rebuilding (so there's never a root password to protect in the first place), a non-root sudo user created, then PermitRootLogin no and PasswordAuthentication no in sshd_config. UFW allows exactly three ports — 22, 80, 443 — everything else denied by default. Fail2ban watches SSH with a 3-attempt threshold and a 1-hour ban. Timezone set to Australia/Brisbane, unattended-upgrades turned on, and a Hetzner snapshot taken of the clean hardened state before any application stack goes on top — the rollback point if a later phase goes wrong.
Cloudflare does DNS and TLS
The domain sits behind Cloudflare, proxied. One wildcard record does all the work:
A * 178.105.184.157 Proxied
— which means every subdomain (demo1, n8n, whatever comes next) is covered automatically, with zero additional DNS entries per site.
Because Cloudflare proxies everything, TLS uses a Cloudflare Origin Certificate rather than Let's Encrypt — 15-year validity, no renewal cron to babysit, and it's what SSL mode Full (Strict) expects on the origin. One shared Nginx snippet holds the cert paths and ciphers, and every virtual host — WordPress or n8n — pulls it in with a single include line instead of repeating the SSL block per site.
n8n, boxed in
n8n runs in Docker, bound to 127.0.0.1:5678 — never exposed directly to the internet. Nginx reverse-proxies n8n.keendigital.com.au to that local port over the shared SSL snippet, with basic auth in front of it. restart: unless-stopped on the container plus Docker enabled at boot means it comes back on its own after a reboot.
Adding a demo site
Each WordPress demo site gets its own isolated database, its own web root under /var/www/[subdomain], a WP-CLI install, and its own Nginx vhost — same SSL snippet, same try_files block, different server_name and root. Nothing is shared between sites except the Nginx config pattern and the underlying hardening.
Why not just use this for everything
A self-managed VPS needs ongoing sysadmin time — patches, PHP versions, Nginx config, cert renewals, monitoring — and that time doesn't bill. Cloudways takes that whole layer off the table for client work, which is worth paying for once a site represents someone else's production traffic. The Hetzner box earns its keep precisely because it only ever hosts things that don't need an SLA: demos, specs, and the automation that runs behind the scenes.
