Last month I got a $25.40 invoice from my VPS provider for hosting a tiny JSON API. The API serves ~180k requests/month, averages 12ms of CPU time per request, and sits idle 96% of the day. I was paying for a server that does almost nothing.
So I moved the whole thing to Cloudflare Workers. New monthly bill: $0.00.
The numbers before and after
| Item | VPS (Hetzner CX11) | Cloudflare Workers Free |
|---|---|---|
| Monthly cost | $4.51 + $20.89 bandwidth | $0 |
| Requests included | unlimited (but I pay for idle) | 100,000/day |
| Cold start | n/a (always on) | ~0ms (V8 isolates) |
| p50 latency (US) | 140ms | 18ms |
| p99 latency | 310ms | 45ms |
| My monthly usage | 180k requests | 180k requests (60% of free quota) |
The latency drop surprised me most. Workers run in 300+ edge locations, so my users in Frankfurt and Singapore stopped round-tripping to a single box in Falkenstein.
The actual migration (took me one evening)
My API is a thin proxy that validates a token, queries a KV store, and returns JSON. The entire worker:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const token = request.headers.get("X-API-Key");
if (!token || !(await env.API_KEYS.get(token))) {
return Response.json({ error: "invalid key" }, { status: 401 });
}
const key = url.pathname.slice(1);
const cached = await env.CACHE.get(key, "json");
if (cached) {
return Response.json(cached, {
headers: { "X-Cache": "HIT" },
});
}
const fresh = await fetchUpstream(key);
// Cache for 5 minutes, fire-and-forget
ctx.waitUntil(env.CACHE.put(key, JSON.stringify(fresh), { expirationTtl: 300 }));
return Response.json(fresh, { headers: { "X-Cache": "MISS" } });
},
};
Deploy is one command:
npm create cloudflare@latest
npx wrangler deploy
# Free tier: 100k requests/day, 10ms CPU per request, Workers KV included
What I gave up (be honest)
- No long-running processes. 10ms CPU limit on free tier (30s on paid). My image-resize endpoint had to move to a queue.
- No raw TCP. No direct Postgres connection — I use Neon over HTTP or Hyperdrive.
-
Node compatibility is good but not perfect. Two npm packages (
bcrypt, one ORM driver) needed swaps.
The controversial part
I now think renting a whole VPS for a low-traffic JSON API in 2026 is a legacy habit, not an architecture decision. Between Workers (100k req/day free), Vercel Functions, and Railway's trial, the "I need a $5 droplet" instinct costs the indie dev scene millions a year in aggregate — for compute that sits idle 96% of the time.
If your API does under ~3M requests/month and is CPU-light, you're probably donating money to Hetzner too.
When I'm prototyping these migrations I lean on MonkeyCode (free, open-source AI coding assistant) to generate the worker boilerplate and wrangler config — saved me the docs-diving: https://ly.cyberserval.tech/iIETXiF
What's your cutoff? At what request volume does a real server actually become cheaper than serverless for you?
Top comments (0)