Migrating a Decade‑Old Ubuntu Blog to FreeBSD on Hetzner
#Infrastructure

Migrating a Decade‑Old Ubuntu Blog to FreeBSD on Hetzner

AI & ML Reporter
4 min read

After ten years on an out‑of‑support Ubuntu 16.04 droplet, the author moved a static‑site stack to a Hetzner VPS running FreeBSD 14.3. Using Bastille to manage jails, Caddy for automatic TLS, and ZFS snapshots, the new setup costs a fraction of the old service and shows markedly better load‑test results, though most of the gain comes from hardware and configuration differences rather than the OS itself.

What was claimed

The blog had been running on a DigitalOcean droplet with Ubuntu 16.04 LTS for more than ten years. The author announced a migration to a Hetzner virtual machine, switched the OS to FreeBSD, and promised lower cost, better performance, and a cleaner security posture using FreeBSD jails and ZFS snapshots.

What is actually new

  • Hardware upgrade – the Hetzner VM provides 4 vCPU, 4 GiB RAM and a 100 Mbps uplink for ~€6 / month, compared to the old 1 vCPU, 2 GiB RAM, 1 Gbps burst limit for $13 / month. The raw CPU and memory increase alone explains most of the throughput improvement.
  • Operating system – FreeBSD 14.3 replaces Ubuntu 16.04. The switch brings a different kernel, the ZFS file system, and the native jail container mechanism.
  • Management tooling – the author uses the Bastille framework to create and maintain jails. Bastille wraps the low‑level jail commands, provides templating, and makes mounting host directories into jails straightforward.
  • Web stack – Caddy replaces nginx as the front‑end reverse proxy. Caddy’s automatic Let’s Encrypt handling removes the need for a separate certbot cron job.
  • Snapshot‑based backups – ZFS snapshots are taken locally, eliminating reliance on Hetzner‑provided snapshots.

Steps that mattered

  1. FreeBSD installation – Hetzner hides the FreeBSD image behind the ISO selector. After mounting the 14.3 ISO and following the installer, the system boots into a clean FreeBSD install.
  2. Bastille bootstrapbastille bootstrap 14.3-RELEASE prepares the host for jail creation.
  3. Network isolation – A cloned loopback interface (bastille0) with address 10.0.0.0/24 isolates jail traffic. PF rules forward HTTP/HTTPS from the external vtnet0 interface to the Caddy jail (10.0.0.5).
  4. Caddy jail – Created with bastelle create caddy …. Caddy is installed via pkg install caddy, enabled, and configured with a simple Caddyfile that redirects the apex domain and reverse‑proxies each site jail.
  5. Site jails – Each site (e.g., es.cro.to, the blog) lives in its own jail based on the www/nginx template. Host directories are mounted read‑only with nullfs to keep the source of truth outside the jail.
  6. Deployment scripts – Minimal shell scripts copy generated static files into the nginx root inside the jail and purge .git directories.

My old Digital Ocean server

Benchmark results

The author ran two load generators, wrk (local to Hetzner) and hey (from remote VPSes), against the old Ubuntu host (crocidb.com) and the new FreeBSD host (crocidb.cro.to).

Test Requests/sec Avg latency
wrk (local) – Ubuntu 833 req/s 89 ms
wrk (local) – FreeBSD 12 260 req/s 6 ms
hey (São Paulo) – Ubuntu ~300 req/s
hey (São Paulo) – FreeBSD ~800 req/s

The FreeBSD server consistently handled three‑to‑eleven times more requests per second. However, the local wrk test benefits from being in the same data centre, inflating the FreeBSD numbers. The remote hey tests, while still favoring FreeBSD, show a smaller margin, suggesting that CPU/memory headroom is the dominant factor.

Limitations and open questions

  • Hardware vs OS – The Hetzner VM has four cores and double the RAM. The Ubuntu droplet had a single core. A fair comparison would require matching hardware, which the author did not do.
  • Kernel tuning – The FreeBSD jail initially hit the default kern.ipc.somaxconn limit (128). Raising it to 16 384 was necessary for high‑concurrency tests. Similar tuning on the Linux side might have narrowed the gap.
  • Network path – PF NAT and the extra loopback interface add a small processing overhead, but the impact appears negligible compared to the CPU difference.
  • Persistence of snapshots – ZFS snapshots are powerful, yet they consume disk space on the same VPS. A full backup strategy still needs off‑site storage.
  • Complexity – Managing multiple jails, PF rules, and ZFS datasets is more involved than a single nginx instance on Ubuntu. For a tiny personal blog, the added operational burden may outweigh the benefits.

Takeaways for practitioners

  1. Don’t migrate for hype alone – The performance uplift came mainly from better hardware and proper kernel tuning. FreeBSD’s jail model is solid, but you’ll see similar gains on Linux with Docker or systemd‑nspawn if you upgrade the host.
  2. Use the right tool for the job – Bastille simplifies jail lifecycle management, but it’s still a niche solution. If you already have container orchestration in place, sticking with Docker may reduce friction.
  3. Snapshot‑driven backups are useful – ZFS provides atomic snapshots and fast rollbacks. For low‑traffic sites, a nightly snapshot plus off‑site rsync is a pragmatic approach.
  4. Caddy can replace certbot – Automatic TLS is a genuine convenience, especially when you have many small sites.
  5. Cost matters – Switching from $13 / month to €6 / month is a tangible win, regardless of the OS.

Final thoughts

The migration demonstrates that moving a static‑site stack to FreeBSD is feasible and, for this author, enjoyable. The performance numbers are impressive, but they should be interpreted as a combination of hardware upgrade, kernel tuning, and the efficiency of the chosen web server. For anyone running a similar low‑traffic blog, the extra complexity of jails may not be justified unless you specifically want to learn FreeBSD or need the isolation it provides.

Featured image

Comments

Loading comments...