Three Cron Trigger incidents in ten days
Cloudflare logged three Workers Cron Triggers incidents in October 2026, all rated minor. A trigger that never fires writes nothing, so none of them showed up in customers' own logs.
13 October: new and edited schedules stop firing
At 15:18 UTC on 13 October Cloudflare posted that "customers who have recently created or modified a Cron Trigger schedule may find that the schedule is not executing as expected." It was resolved at 00:09 UTC the next day. Nearly nine hours.
Schedules nobody had touched kept firing. The ones that broke were the ones somebody had just shipped.
A third-party tracker lists a follow-up on 14 and 15 October that Cloudflare's own incident feed does not show. Treat that one as unconfirmed.
16 and 23 October: missing, late and doubled runs
Just after midnight UTC on 16 October a second incident warned that a subset of customers might not get their cron invoked. It cleared in about an hour.
The third opened at 15:27 UTC on 23 October: triggers "running late, being skipped, or running more than once." Fixed at 20:29, resolved at 20:37. A job that tolerates late runs can still break on a double run, so read that list as separate problems.
Why the schedule you just changed is the fragile one
A schedule edit is a deploy to a different system
You change the crons array in your Wrangler config and deploy:
[triggers]
crons = ["30 2 * * *", "0 */6 * * *"]
The code ships with the Worker. The schedule is registered with Cloudflare's scheduler separately, and the docs say adding, updating or deleting a trigger can take up to 15 minutes to reach the network. A green deploy proves the Worker was uploaded. Whether the scheduler took the new expression is a different question, and on 13 October the answer was no for nine hours.
Old schedules kept firing, so everything looked fine
Run ten triggers, edit one, and nine keep producing logs. The one you changed goes quiet, often a new job with no monitor yet.
The dashboard is slow to help. Past Cron Events keeps the 100 most recent invocations, and after creating or renaming a Worker it can take up to 30 minutes before anything appears there.
The same gap outside Cloudflare
systemd timers after an edit
On a Linux host the same mistake is editing a .timer unit and stopping there. systemd keeps the old definition until you reload:
sudo systemctl daemon-reload
sudo systemctl restart nightly-report.timer
systemctl list-timers nightly-report.timer
If the NEXT column still shows the old slot, the change did not take. More on that in Monitoring systemd Timers.
GitHub Actions schedules that never register
In a GitHub community thread about late scheduled workflows, one user reported on 25 September that newly registered schedules produced zero runs while older ones still fired. GitHub has not replied, so it stays a user report. The delays are covered in our GitHub Actions post.
Verifying a schedule change actually took
Read back what the platform registered
Your config describes what you asked for. Ask the platform what it has:
curl -fsS \
-H "Authorization: Bearer $CF_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/workers/scripts/nightly-report/schedules"
Each entry has the cron expression with created_on and modified_on. Run it in CI after deploy and fail the pipeline when the expressions differ from your Wrangler file. That catches a schedule that never registered. It won't catch one that registered and still doesn't fire, which is what 13 October looked like.
Move the monitor in the same change
Move a job from 02:30 to 04:00, leave the heartbeat monitor on 02:30, and the first night brings a false alert. Do that twice and people stop reading alerts. Change the monitor's expected schedule in the same pull request as the crons array. Cron Triggers run in UTC only, so set the monitor in UTC.
Then watch the first run after the change yourself. That first slot is exactly what broke this month.
Duplicates are part of the contract now
Key the work on scheduledTime
The scheduled() handler gets a controller carrying the cron expression that matched and the scheduledTime the run was meant for. Log both. And since 23 October showed triggers firing twice, use that slot as the idempotency key:
export default {
async scheduled(controller, env, ctx) {
const slot = new Date(controller.scheduledTime).toISOString();
const claim = await env.DB.prepare(
"INSERT INTO report_runs (slot) VALUES (?) ON CONFLICT (slot) DO NOTHING"
).bind(slot).run();
if (claim.meta.changes === 0) {
console.log(`duplicate delivery for ${slot} (${controller.cron}), skipping`);
return;
}
await buildReport(env, slot);
await fetch("https://cronguard.app/api/ping/nightly-report");
},
};
A second delivery for the same slot finds the row and exits. The ping goes out only after the report is built, so a skipped slot and a schedule that never registered look identical from outside. No check-in.
The cost: a run that dies halfway has already claimed its slot and will not be retried. The missing ping tells you to rerun it by hand. Give the monitor enough grace to absorb a late run.
Frequently asked questions about Cloudflare Cron Trigger incidents
What went wrong with Cloudflare Cron Triggers in October 2026? Cloudflare logged three Workers Cron Triggers incidents. On 13 October new or modified schedules did not run as expected for nearly nine hours, on 16 October some triggers were not invoked for about an hour, and on 23 October some ran late, were skipped or ran twice.
Why were only new or edited schedules affected on 13 October? Cloudflare said recently created or modified schedules might not execute, while untouched ones kept running. The schedule is registered separately from the Worker code, so a green deploy does not prove the new schedule is live.
How long does a Cron Trigger change take to apply? Cloudflare's documentation says adding, updating or deleting a Cron Trigger can take up to 15 minutes to propagate. After creating or renaming a Worker, Past Cron Events can take up to 30 minutes to show anything.
How do I check that a new schedule actually registered? Read the registered schedules back from the Cloudflare API after deploying and compare them with your Wrangler config. Then watch the first scheduled run, because a registered schedule can still fail to fire.
How do I stop a duplicated trigger from running a job twice? Use the scheduledTime the handler receives as the key for the run. Record that slot with an insert that does nothing on conflict, and a second delivery for the same slot exits early.
Further reading
- Scheduling Cron Jobs on Cloudflare Workflows Without a Server
- Dead Man's Switch Monitoring: The Only Reliable Way to Watch Cron Jobs
- Preventing Duplicate Cron Jobs When You Scale to Multiple Servers
Conclusion: The first of October's incidents hit only schedules people had just changed, which is when a job is least likely to have a monitor. Treat every edit to a crons array as a deploy, and do not call it done until the next run has checked in.
Sources: Cloudflare Status: Cron Trigger Activation Issues (13 October 2026), Cloudflare Status: Workers Cron Triggers issues (16 October 2026), Cloudflare Status: Delayed, missed, and duplicated Workers Cron Triggers (23 October 2026), Cloudflare Workers Cron Triggers docs, GitHub community discussion 207346.