GUIDE · CLOUDFLARE WORKERS · CRON TRIGGERS · FREE

Monitor Cloudflare Workers Cron Triggers — detect failed scheduled runs

A Cloudflare Cron Trigger invokes your Worker's scheduled() handler on a cron schedule. If that handler throws — an unhandled exception, a failed subrequest, a timeout — Cloudflare marks the invocation as errored in Workers Analytics, but sends you nothing. If the trigger silently stops firing, you also get nothing. Add one fetch ping inside the handler and LastPing catches every failure and every missed run in seconds.


Setup: one fetch in your scheduled() handler

Ping on success, ping /fail in the catch block. Works from any Worker runtime.

Create a heartbeat monitor in LastPing

In the console, create a monitor with the same cron schedule as your trigger. Cloudflare crons run in UTC, so set the monitor's timezone to UTC. Copy the ping URL.

https://ping.lastping.dev/<your-monitor-id>

Declare the cron in wrangler.toml

Set your schedule and add the monitor id as a Worker variable (or a secret via wrangler secret put):

# wrangler.toml name = "nightly-job" main = "src/index.js" [triggers] crons = ["0 3 * * *"] # 03:00 UTC daily [vars] LASTPING_ID = "<your-monitor-id>"

Ping from the scheduled() handler

Ping on success; fetch the /fail path on error, then re-throw so the invocation still shows as errored in Cloudflare too:

// src/index.js export default { async scheduled(event, env, ctx) { const ping = `https://ping.lastping.dev/${env.LASTPING_ID}`; try { await doWork(env); await fetch(ping); // success } catch (err) { await fetch(`${ping}/fail`, { // failure method: "POST", body: String(err && err.stack || err), }); throw err; } }, };

Use waitUntil for the ping if you return early

If your handler returns before the ping resolves, wrap it in ctx.waitUntil(fetch(ping)) so the runtime keeps the request alive until the ping completes.


What this catches

  • Handler throws — the success ping is skipped (or the /fail ping fires); silence in the grace window opens an incident.
  • Trigger stops firing — a disabled or removed Cron Trigger produces no ping → incident opens.
  • Silent no-op — validate the work actually happened and only ping on confirmed success.
  • Recovery — the next successful invocation closes the incident automatically.

Also on Fly.io? See Fly.io scheduled machines. On Render? See Render cron jobs. For the general pattern, see Monitor cron jobs, and Node.js scripts for the runtime details.


Questions people ask

Front-loaded answers — the most important fact first.

  • Does Cloudflare alert when a Cron Trigger fails?

    No. A throwing scheduled() handler is recorded as an errored invocation in Workers Analytics and the dashboard, but no notification is sent. If the trigger stops firing entirely, nothing alerts you. A dead-man's-switch ping catches both — it expects a check-in on every run and opens an incident when one doesn't arrive.

  • What timezone do Cloudflare Cron Triggers use?

    Always UTC. There is no per-Worker timezone setting, so write your cron expression in UTC and set the LastPing monitor to UTC as well.

  • Will the ping count against my subrequest limit?

    The ping is a single outbound fetch, which counts as one subrequest — negligible against the per-invocation limit. Use ctx.waitUntil() if the handler might return before the ping resolves.

  • Is this free?

    Yes. LastPing is free and fully hosted with no monitor cap and no paid tier. See vs Healthchecks.io and vs Cronitor.

FREE · FULLY HOSTED · NO PAID TIER

A Worker cron that throws tells no one. One fetch changes that.

Ping from your scheduled() handler. First alert in under a minute — and LastPing monitors itself the same way.