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.
Your assistant can do this for you
Monitor my Cloudflare Workers cron triggers with LastPing
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. When a
scheduled()handler throws, Cloudflare records the invocation as errored in Workers Analytics and the dashboard, but sends no notification. If the Cron Trigger stops firing entirely, nothing alerts you. A dead-man's-switch ping catches both: it expects a check-in on every scheduled run and opens an incident when one doesn't arrive. -
How do I add monitoring to a Cloudflare Worker cron?
Inside the
scheduled()handler,await fetch('https://ping.lastping.dev/<id>')after the work succeeds, and fetch the/failpath in a catch block. Create a LastPing monitor with the same cron schedule. If no ping arrives within the grace window, an incident opens. -
What timezone do Cloudflare Cron Triggers use?
Always UTC: Cloudflare Cron Triggers are always evaluated in 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. Usectx.waitUntil()if the handler might return before the ping resolves. -
Is this free?
Yes. LastPing is free for individuals with no monitor cap. See vs Healthchecks.io and vs Cronitor.
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.