Last month I got the renewal notice for my VPS: $45/month, $540/year, for a box running a personal API, a cron scraper, and a tiny Discord bot. I ran the numbers and realized the whole thing could live on Cloudflare Workers for $0.
I migrated it over a weekend. Here is exactly what happened, with the numbers.
The Before Stack
| Item | Cost |
|---|---|
| VPS (2 vCPU, 4GB RAM) | $45/mo |
| Managed Postgres | $15/mo |
| Total | $60/mo = $720/yr |
The VPS was doing: a FastAPI endpoint (~200 requests/day), a scraper that ran every 6 hours, and a Discord bot that replied to maybe 30 messages a day. Embarrassingly low traffic for $720/year.
The Migration
Cloudflare Workers free tier gives you 100,000 requests/day. My entire workload was ~5,000 requests/day. That is 5% of the free quota.
The FastAPI endpoint became a Worker in about 40 lines:
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === "/api/quote") {
const row = await env.DB.prepare(
"SELECT * FROM quotes ORDER BY RANDOM() LIMIT 1"
).first();
return Response.json(row);
}
return new Response("not found", { status: 404 });
},
// The cron scraper replaced my cron daemon:
async scheduled(event, env) {
const data = await fetch("https://source.example.com/feed");
await env.KV.put("latest", await data.text());
},
};
Wrangler config for the cron trigger (runs every 6 hours, same as the old crontab):
name = "my-api"
main = "src/index.js"
compatibility_date = "2026-07-01"
[triggers]
crons = ["0 */6 * * *"]
[[d1_databases]]
binding = "DB"
database_name = "quotes-db"
The Postgres database moved to D1 (SQLite at the edge, 5GB free). The bot's state moved to KV (100k reads/day free).
The Honest Numbers After 30 Days
| Metric | VPS | Cloudflare Workers |
|---|---|---|
| Monthly cost | $60 | $0 |
| Cold start | 0ms (always on) | ~5ms (V8 isolates, not containers) |
| p95 latency | 180ms | 45ms |
| Free quota used | — | 5.2% |
| Uptime | 99.7% (one reboot) | 100% |
Latency actually improved because Workers run in 300+ edge locations instead of one Frankfurt datacenter.
What Didn't Fit (Be Honest)
Not everything can move:
- Long-running jobs — Workers cap CPU time at 30s on the free tier (10ms default, configurable). My video-encode side project stays on a cheap box.
- WebSockets — need Durable Objects, which is a different mental model.
- Big dependencies — 1MB compressed bundle limit on free tier. No pandas, no Chromium.
If your workload fits in those constraints — and most personal projects, webhooks, bots, and light APIs do — paying for a VPS in 2026 is a donation.
The Controversial Take
For 90% of side projects, "I need a server" is a 2015 reflex. The serverless free tiers (Workers, Vercel, Netlify) are now generous enough that the default question should be: why would this ever need to cost money?
I saved $720/year. My API got faster. My weekend project no longer has an uptime babysitter.
What's still keeping you on a paid VPS — real requirements, or just habit?
While migrating, I used MonkeyCode (free, open-source AI coding assistant) to port the FastAPI routes to Workers syntax — saved me a couple of hours of docs-reading: https://ly.cyberserval.tech/iIETXiF
Top comments (0)