Monitor Cloudflare Workers Cron Triggers

A Cron Trigger that stops firing, or a scheduled() handler that throws, is easy to miss. Ping a URL after the work completes and we email you when a run is late or fails.

You need: a Lateping account, a Worker with a Cron Trigger, about 5 minutes.

What can go wrong

A Worker’s scheduled run leaves no trace you’d notice unless you go and look at the logs.

Set it up

  1. Create a check

    Create a check in Lateping with the same cron expression as the trigger and the timezone UTC (Cron Triggers run in UTC). Copy its ping URL into PING_URL below.

  2. Ping from the handler

    src/index.js
    const PING_URL = "https://lateping.com/p/<check-id>";
    
    export default {
      async scheduled(controller, env, ctx) {
        await ping("/start");
        try {
          await runJob(env);
        } catch (err) {
          await ping("/fail");
          throw err; // keep the run marked as failed in Workers logs
        }
        await ping("");
      },
    };
    
    async function runJob(env) {
      // Your scheduled work goes here. Await everything it starts.
    }
    
    async function ping(path) {
      try {
        const res = await fetch(PING_URL + path, {
          method: "POST",
          signal: AbortSignal.timeout(10_000),
        });
        if (!res.ok) console.error(`Lateping ping ${path || "/"} returned ${res.status}`);
      } catch (err) {
        console.error(`Lateping ping ${path || "/"} failed: ${err}`);
      }
    }
    
    wrangler.jsonc
    {
      "name": "nightly-job",
      "main": "src/index.js",
      "compatibility_date": "2026-09-01",
      "triggers": {
        "crons": ["17 2 * * *"]
      }
    }
    
    • The success ping is sent only after runJob has finished without throwing.
    • If runJob throws, the catch sends /fail, which alerts straight away, then rethrows so the failure still shows in your Workers logs.
    • If the trigger never fires, no ping arrives and the check alerts after the grace period.
    • ping() never throws. A network problem reaching Lateping is logged, but it does not turn a successful job into a failed one.
  3. Test it locally

    Start the dev server with scheduled testing on, then trigger the handler from a second terminal. The check should show a start ping and a success ping.

    bash
    npx wrangler dev --test-scheduled
    
    bash
    curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=17+2+*+*+*"
    

Await the ping, or use ctx.waitUntil()

Don’t call fetch() for the ping without awaiting it. Once the handler returns, the runtime can end the invocation before an un-awaited request is sent. Either await it, as above, or pass the promise to ctx.waitUntil() so the runtime keeps the invocation alive until it settles. If you hand the whole job to ctx.waitUntil(), put the pings inside that same promise chain, after the work, so the success ping still waits for the job.

Notes

  • Set the grace period to longer than the job’s normal run time plus a few minutes of start delay. Shorten it once you have a week of real run times in the dashboard.
  • One check per trigger. If a Worker has several crons, branch on controller.cron and give each schedule its own check URL.
  • Alerts go by email. On Pro and above they can also go to Slack, Discord or a webhook.

Next steps

On this page