Why I Self-Host My SaaS Apps on Hetzner
Update, May 21, 2026: I have moved more projects since writing this. My main products and most older projects now run on Hetzner + Kamal. A few projects are still exceptions, but Hetzner is the default now. I also published a newer cost breakdown: Hetzner vs Vercel: What I Pay to Run My SaaS Apps.
I run my SaaS apps on Hetzner with Kamal. I used Heroku years ago, then Render for a couple of years. Both were fine for deployment. I switched to self-hosting for cost, control, and performance.
Cost
My base Hetzner bill is low enough that I can run multiple apps with PostgreSQL, background workers, and the whole stack on small ARM servers without thinking about per-project hosting cost.
The cost stays flat. More traffic doesn’t change my bill — I’m paying for the server, not per-request or per-seat. If I need more capacity, I bump to the next server tier. Still cheaper than any PaaS.
Control
On my server, I pick the OS, the Docker version, the PostgreSQL config, the backup schedule, the firewall rules. And I make those decisions once — they’re encoded in Terraform and Kamal configs. Spinning up a new project is the same setup, same commands. I’m not dependent on a platform’s roadmap or pricing decisions.
Performance
My app and database run on the same machine. Database queries are sub-millisecond — no network hop. On most PaaS setups, the database is a separate service with network overhead on every query.
A small Hetzner ARM server gives me dedicated vCPUs and RAM.
DevOps in 2026
Setting up a server in 2015 meant configuring Nginx, managing SSL certificates by hand, writing systemd service files, and hoping you didn’t miss a security update.
Now it’s Docker and Kamal. I define my app in a Dockerfile, my infrastructure in Terraform, and my deployment in a Kamal config. One command provisions the server. One command deploys the app. SSL is automatic via Let’s Encrypt through kamal-proxy.
I spend maybe 30 minutes a month on maintenance — OS updates, checking disk space, reviewing logs. My Telegram bot pings me if anything needs attention.
Scaling
A single Hetzner ARM server handles far more traffic than most indie SaaS apps will see. PostgreSQL on one server can do thousands of queries per second.
If I outgrow one server — a great problem to have — Kamal supports multi-server deployments. Move the database to its own box, add a second web server, and kamal-proxy load-balances between them.
What I Self-Host (and Don’t)
- My main products and most older projects — Vue frontends, Fastify APIs, PostgreSQL, background workers
- Automated daily PostgreSQL dumps to object storage
- Monitoring via Uptime Robot and PostHog
What I don’t self-host:
- This blog — Jekyll on GitHub Pages, because it’s static and free
- Email — transactional email goes through a service. Self-hosting email is a deliverability nightmare
- CDN/edge — Cloudflare sits in front of the server for caching and DDoS protection
Getting Started
I wrote about the specific tools in detail:
- Terraform setup — provision a Hetzner server with four files
- Kamal deployment — zero-downtime deploys with one command
The whole thing took about two days the first time. Subsequent projects take an hour or two — and most of that time is setting up API keys for external services, not the hosting itself. After that, every deploy is kamal deploy.