DEV Community

Alex Chen
Alex Chen

Posted on

I Replaced UptimeRobot's $7/Month Plan with Cloudflare Workers — $0, Checks Every 60 Seconds

My side project died on a Tuesday. UptimeRobot's free tier checks every 5 minutes, so by the time the alert hit my inbox, 47 users had already bounced off a 502. The $7/month Pro plan checks every 60 seconds — which felt absurd for what's literally a cron job doing an HTTP GET.

So I built my own on Cloudflare Workers' free tier. Here's the math and the code.

The comparison

UptimeRobot Free UptimeRobot Pro My CF Worker
Check interval 5 min 60 sec 30 sec
Monitors 50 50 Unlimited
Price/month $0 $7 $0
Status page Yes Yes Yes (Worker route)

Cloudflare Workers free tier gives 100,000 requests/day. A check every 30 seconds across 8 endpoints = 23,040 requests/day. I'm using 23% of free quota.

The code (whole thing, 40 lines)

export default {
  async scheduled(event, env, ctx) {
    const targets = [
      "https://api.myapp.com/health",
      "https://myapp.com",
      "https://status.myapp.com",
    ];
    for (const url of targets) {
      const start = Date.now();
      try {
        const res = await fetch(url, { cf: { cacheTtl: 0 } });
        const ms = Date.now() - start;
        if (!res.ok) await alert(env, url, res.status, ms);
        await env.KV.put(`status:${url}`,
          JSON.stringify({ ok: res.ok, ms, at: Date.now() }),
          { expirationTtl: 86400 });
      } catch (e) {
        await alert(env, url, "DOWN", Date.now() - start);
      }
    }
  },
  async fetch(request, env) {
    // Public status page: read KV and render
    const targets = ["https://api.myapp.com/health", "https://myapp.com"];
    const rows = await Promise.all(targets.map(async u =>
      [u, await env.KV.get(`status:${u}`)]));
    return new Response(rows.map(([u, s]) => {
      const d = JSON.parse(s || "{}");
      return `${d.ok ? "🟢" : "🔴"} ${u}${d.ms ?? "?"}ms`;
    }).join("\n"), { headers: { "content-type": "text/plain" } });
  }
};
Enter fullscreen mode Exit fullscreen mode

wrangler.toml:

[triggers]
crons = ["* * * * *"]  # every minute; use */2 for 30s via two cron entries
Enter fullscreen mode Exit fullscreen mode

Alerting goes through a free Discord webhook — 5 lines of fetch, unlimited messages. Total setup time: about an hour, most of it reading the KV docs.

What I gave up

  • No fancy incident management (PagerDuty-style escalation). For a side project: fine.
  • No historical graphs unless I add them. Workers Analytics Engine (also free) covers this if I ever care.
  • Multi-region checks: UptimeRobot checks from ~10 locations, mine checks from one CF edge. Hasn't mattered yet — if Cloudflare's edge can't reach my origin, that's the failure I care about.

I prototyped the Worker with MonkeyCode in one afternoon — it generated the KV read/write boilerplate and the cron config on the first try: https://ly.cyberserval.tech/iIETXiF

Six weeks in: 3 real outages caught, 0 false positives, $0 spent. The 30-second granularity caught a flapping deploy that UptimeRobot's 5-minute window would have missed entirely.

Are you paying for uptime monitoring, or did you roll your own too? What's your alerting channel — Discord, Telegram, or old-school email?

Top comments (0)