DEV Community

Alex Chen
Alex Chen

Posted on

I Replaced My $45/Month VPS with Cloudflare Workers. My Bill Is $0 and It Got Faster.

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());
  },
};
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)