VPS Migration Retrospective
VPS Migration: Vercel to Hetzner
Why I Left
Vercel is great until you need control. Custom headers? Limited. Server-side logic beyond edge functions? Complicated. Running a second app? Another project, another bill. And $20/month for a portfolio site felt like paying rent on an apartment I didn't fully own.
$6.65/month gets me a real server. Root access. Run whatever I want. No cold starts. No surprise invoices.
The Stack
| Component | Choice | Why |
|---|---|---|
| Server | Hetzner CPX11 (2 vCPU, 2GB RAM) | Cheapest reliable option. Ashburn VA for US latency. |
| OS | Ubuntu 24.04 LTS | Boring. Stable. That's the point. |
| Reverse Proxy | Caddy | Auto-SSL with zero config. 3 lines for HTTPS. |
| Process Manager | PM2 | Keeps Node alive, handles restarts, manages logs. |
| DNS | Cloudflare (proxied) | Free DDoS protection, edge caching. |
| Database | PostgreSQL 16 | Overkill for now. Ready when I need it. |
Key Decisions
Caddy over Nginx. Nginx is the standard. Caddy is the shortcut. Automatic HTTPS provisioning, readable config, less than 5 minutes to set up. For a solo builder running one site, Caddy wins every time.
PM2 over systemd. Better Node.js integration. Built-in log rotation. Cluster mode if I ever need it. pm2 restart tcv is easier to type at midnight than debugging a systemd unit file.
Ed25519 SSH keys. Faster, shorter, more secure than RSA. Root login disabled. UFW firewall. Basic stuff, but basic stuff is what prevents stupid problems.
What Went Well
The entire migration took one evening. DNS propagation was the longest part. Caddy provisioned SSL certificates automatically. The site loads faster now because there are no cold starts. Serverless functions used to spin up in 1-3 seconds. Now it's instant because the server is always running.
What I'd Do Differently
Set up automated backups before the first deploy, not after. I added a cron job later, but that "later" was a week of running without backups. Stupid risk for zero reason.
Write the deploy script first. I deployed manually 3 times before writing deploy.sh. Each time I forgot a step. The script took 5 minutes to write and has saved me from myself every deploy since.
The Numbers
| Metric | Vercel | Hetzner |
|---|---|---|
| Monthly cost | $20 (Pro) | $6.65 |
| Cold start | 1-3 seconds | None |
| Control | Limited | Full root |
| Deploys | Git push (automatic) | SSH + script (10 seconds) |
I'm never going back to managed hosting for a site this simple. The 10 seconds of manual deploy is worth full ownership of the stack.