DEV Community

quietpulse
quietpulse

Posted on • Originally published at quietpulse.xyz

Cloudflare Workers Cron Monitoring: How to Catch Missed Triggers Before They Break Production

Cloudflare Workers Cron Monitoring matters because scheduled edge jobs can fail quietly while the rest of your app looks healthy.

Your website can be up. Your API can return 200 OK. The Worker can be deployed. But the Cron Trigger that refreshes cached data, syncs records, sends reports, or cleans old state may have stopped completing successfully hours ago.

That is the monitoring gap with cron-like systems: normal uptime checks tell you whether a public endpoint responds. They do not tell you whether scheduled background work actually happened.

The problem

Cloudflare Workers Cron Triggers are commonly used for small but important recurring tasks:

  • refreshing cached data
  • syncing from third-party APIs
  • generating reports
  • cleaning expired records
  • updating search indexes
  • sending webhook retries
  • warming edge data before traffic arrives

Many of these jobs do not have a public URL. Cloudflare invokes the Worker on a schedule, the code runs, and the result is visible only through logs, metrics, or downstream state.

If the job stops running or fails halfway through, your normal uptime monitor may stay green.

That is a silent failure.

The system is not fully down, but something important stopped happening.

Why it happens

A scheduled Cloudflare Worker can fail for several practical reasons.

Configuration can be wrong. The cron expression may not match the intended schedule. The trigger may exist in staging but not production. A deployment may accidentally remove or change the scheduled handler.

Runtime code can fail. The Worker may throw while calling an API, parsing JSON, writing to KV, D1, R2, or an external database.

Dependencies can fail. Third-party APIs can return errors, rate limits, malformed responses, or slow timeouts.

Jobs can also partially succeed. A Worker may process some records, skip others, log an error, and exit in a way nobody notices until stale data shows up.

A simple scheduled Worker might look like this:

export default {
  async scheduled(controller, env, ctx) {
    await refreshCache(env);
  },
};
Enter fullscreen mode Exit fullscreen mode

That code may be fine. But nothing in it tells you that refreshCache() completed successfully every time it was expected to run.

Why it's dangerous

Missed Cron Triggers usually break business logic, not basic availability.

A failed scheduled job can mean:

  • stale data remains visible
  • reports are not generated
  • usage is not synced
  • cleanup tasks do not run
  • exports are missing
  • old records pile up
  • customers see outdated information

The delay is what makes it painful.

If a public API goes down, someone notices quickly. If an hourly scheduled Worker fails silently, the first symptom may appear much later. By then you are digging through logs and trying to reconstruct what happened.

Logs help with investigation. They do not always help with detection.

How to detect it

The simplest detection pattern is heartbeat monitoring.

Instead of asking, “Is my website up?”, heartbeat monitoring asks:

Did this specific scheduled job finish successfully within the expected time window?

For Cloudflare Workers Cron Monitoring, the flow is:

  1. Create a heartbeat check for the job schedule.
  2. Run the scheduled Worker normally.
  3. Send a heartbeat ping after the job completes successfully.
  4. Alert if the ping does not arrive on time.

The key detail is that the ping should happen at the end, not the beginning.

A heartbeat at the start only proves the Worker began running. It does not prove the work finished.

Simple solution

Here is a basic Cloudflare Worker scheduled handler with a completion heartbeat:

export default {
  async scheduled(controller, env, ctx) {
    await runScheduledJob(env);

    await fetch(env.QUIETPULSE_PING_URL);
  },
};

async function runScheduledJob(env) {
  const response = await fetch("https://api.example.com/data");

  if (!response.ok) {
    throw new Error(`API request failed: ${response.status}`);
  }

  const data = await response.json();

  await saveDataSomewhere(env, data);
}

async function saveDataSomewhere(env, data) {
  // Write to KV, R2, D1, an external API, or another storage system.
}
Enter fullscreen mode Exit fullscreen mode

The heartbeat URL can be stored as an environment variable:

https://quietpulse.xyz/ping/YOUR_TOKEN
Enter fullscreen mode Exit fullscreen mode

The job runs first. The heartbeat is sent only after the useful work completes.

If the Worker throws before that point, the ping is not sent. The missing ping becomes the alert signal.

A slightly more explicit version:

export default {
  async scheduled(controller, env, ctx) {
    try {
      await refreshImportantData(env);
      await fetch(env.QUIETPULSE_PING_URL);
    } catch (error) {
      console.error("Scheduled Worker failed", error);
      throw error;
    }
  },
};

async function refreshImportantData(env) {
  const response = await fetch("https://api.example.com/latest");

  if (!response.ok) {
    throw new Error(`Upstream API failed with ${response.status}`);
  }

  const payload = await response.json();

  // Store or process the payload here.
}
Enter fullscreen mode Exit fullscreen mode

If one Worker handles multiple Cron Triggers, use separate heartbeat checks:

export default {
  async scheduled(controller, env, ctx) {
    switch (controller.cron) {
      case "0 * * * *":
        await hourlySync(env);
        await fetch(env.HOURLY_SYNC_PING_URL);
        break;

      case "0 2 * * *":
        await dailyCleanup(env);
        await fetch(env.DAILY_CLEANUP_PING_URL);
        break;

      default:
        console.log(`No handler for cron: ${controller.cron}`);
    }
  },
};
Enter fullscreen mode Exit fullscreen mode

Separate checks make alerts more useful. “Hourly sync missed a run” is better than “some scheduled Worker may have failed.”

Instead of building all the heartbeat timing, grace periods, and alert delivery yourself, you can use a simple heartbeat monitoring tool like QuietPulse. Create a check, copy the ping URL, and call it after your Cloudflare Worker Cron Trigger finishes successfully. If the expected ping is missing, QuietPulse can notify you through the alert channels you configured.

Common mistakes

1. Monitoring only the website

A public uptime monitor does not prove that a scheduled Worker ran. Use uptime checks for public URLs and heartbeat checks for scheduled jobs.

2. Pinging before the work is done

If you send the heartbeat at the start, the monitor can show success even when the job fails later.

Send the ping after successful completion.

3. Swallowing errors and still pinging

Avoid this pattern:

try {
  await runJob();
} catch (error) {
  console.error(error);
}

await fetch(env.QUIETPULSE_PING_URL);
Enter fullscreen mode Exit fullscreen mode

The job failed, but the heartbeat still says success.

4. Sharing one monitor across unrelated jobs

Different schedules should usually have different heartbeat checks. It makes alerts easier to understand and act on.

5. Forgetting time zones

Be careful with cron expressions and expected run times. Document whether the schedule is intended to match UTC or a business timezone.

Alternative approaches

Logs

Cloudflare logs are useful for debugging after an alert. They are less useful as the only way to notice a missed run.

Dashboard metrics

Metrics can show invocations and errors, but they may not map directly to “this business job completed successfully every hour.”

Downstream state checks

You can monitor the output of the job, such as a timestamp in storage or a recently updated file. This is powerful but often more custom than a heartbeat ping.

Status endpoint

Some teams expose an endpoint that reports the last successful run time. An external monitor checks whether that timestamp is fresh. This works well, but for simple jobs a heartbeat ping is usually less code.

FAQ

What is Cloudflare Workers Cron Monitoring?

Cloudflare Workers Cron Monitoring means checking whether scheduled Cloudflare Worker jobs run and complete successfully. Heartbeat monitoring is a common way to do this.

Can uptime monitoring detect missed Cloudflare Cron Triggers?

Not reliably. Uptime monitoring checks public endpoints. A Cron Trigger can fail while the rest of your app stays online.

Where should the heartbeat ping go?

After the scheduled work finishes successfully. If the job fails, the success heartbeat should not be sent.

Should every Cron Trigger have its own heartbeat?

Usually yes. Separate heartbeat checks make alerts clearer and easier to debug.

Are logs enough?

Logs are helpful for investigation, but they are not always enough for alerting. A heartbeat check detects the missing successful run directly.

Conclusion

Cloudflare Workers Cron Triggers are great for lightweight scheduled work, but they still need monitoring.

If a job matters, make it report successful completion. Send a heartbeat after the work finishes, alert when the heartbeat is missing, and treat scheduled jobs as production systems — not background magic.


Originally published at https://quietpulse.xyz/blog/cloudflare-workers-cron-monitoring

Top comments (0)