The move off the crontab
For years the answer to "run this every night" was a line in a crontab on a box you owned: a host, a cron daemon, and some way to notice when either stopped. Cloudflare Workers chipped away at that with Cron Triggers, and Workflows has now taken the next step. A recurring job can be a durable, multi-step Workflow that Cloudflare schedules for you, with no server and no daemon in the picture.
Convenient. It also relocates every silent-failure problem cron ever had onto a platform where you cannot log in to check.
What changed on June 2
On June 2, 2026 Cloudflare shipped the ability to attach cron schedules directly to a Workflow binding. You add a schedules array in wrangler.jsonc and each matching cron expression spins up a fresh Workflow instance on that cadence. No separate scheduled handler, no glue Worker to forward the event.
{
"workflows": [
{
"name": "nightly-jobs",
"binding": "MY_WORKFLOW",
"class_name": "MyWorkflow",
"schedules": ["0 * * * *", "*/15 * * * *", "0 9 * * MON-FRI"]
}
]
}
Cloudflare aims this at exactly the work that used to live in cron: database backups, invoice generation, report aggregation, cleanup tasks. The instance reads its timing from event.schedule.
What scheduled Workflows give you
A Workflow instance is durable: it runs in checkpointed steps, and a step that throws is retried automatically up to a configured ceiling. A backup that was one fragile bash script becomes steps that each survive a crash and resume where they left off.
You also stop maintaining the host. No cron daemon to patch, no PATH that differs from your shell, no full disk turning a backup into a zero-byte file. A managed runtime retires those.
What the move takes away
Every win costs you something, and none of it announces itself.
No host to inspect
When a crontab job misbehaves you log in, read /var/log/syslog, check the exit code. On Workflows there is no host and no syslog. Your only window is the dashboard and the API, and both show what Cloudflare recorded. If a scheduled instance is never created, there is nothing to inspect at all.
Timing is opportunistic, and misses are silent
Cloudflare publishes no timing guarantee for scheduled execution. The Workers docs note that jobs run on underutilized capacity, which is a polite way of saying scheduling is opportunistic. A 0 * * * * schedule means roughly hourly, not the top of every hour to the second. Fine for cleanup, wrong for a job another system expects on time. And nothing alerts you when an expected run fails to appear.
The retention window is not an archive
Instance state is retained for 3 days on the free plan, 30 on paid. That is a debugging window, not a record. A nightly job that started failing five weeks ago on the free plan aged out of the evidence long before anyone noticed.
A step can exhaust its retries and move on
Automatic retries are a feature until they hide a persistent fault. A step is retried up to its ceiling, and when that is exhausted the instance fails. Unless someone watches for that terminal state, an upload step red for a week looks exactly like one that never needed to run.
One account-wide ceiling on schedules
Schedules are capped at 100 cron expressions per account, shared across every Workflow. Concurrent instances are capped too, at 100 on the free plan and 50,000 on paid. These are account-wide, so one noisy Workflow can crowd out another.
Keep the dead man's switch
The pattern that catches silent cron failures on a Linux box catches them here too: watch for the absence of success instead of enumerating every way a run can fail. Cloudflare retries your steps and records what it saw, but it will not tell you that Tuesday's run never started or that a step has failed every retry since Sunday. A check-in that only fires on success will.
Wiring a check-in into the final step
Make the last step ping a monitor, so the check-in only happens when every step before it succeeded. Running inside a durable step, the ping is itself retried if the network blips.
export class MyWorkflow extends WorkflowEntrypoint {
async run(event, step) {
await step.do("dump", async () => dumpDatabase());
await step.do("upload", async () => uploadBackup());
// Only reached if every step above succeeded
await step.do("check-in", async () => {
await fetch("https://cronguard.app/api/ping/your-monitor-id");
});
}
}
Give the monitor the same cadence as the schedule and a grace period wide enough to absorb opportunistic timing. If the check-in does not arrive in that window you hear about it, whether the cause was a missed schedule, a failed step, or an instance that was never created.
When scheduled Workflows fit, and when they do not
Reach for scheduled Workflows when the job is multi-step and tolerates approximate timing: backups, report generation, cleanup. Keep it on a plain crontab or systemd timer when you need tight timing or the work is one quick command. Whichever you pick, monitor the outcome from outside the system that runs it. A scheduler reporting on its own health is the one report you cannot trust.
Frequently asked questions about scheduling cron jobs on Cloudflare Workflows
How do I run a Cloudflare Workflow on a cron schedule? Add a schedules array to the Workflow binding in your wrangler.jsonc, listing one or more cron expressions. Each matching expression creates a new Workflow instance on that cadence, and the instance reads its timing from event.schedule. This shipped on June 2, 2026.
Are Cloudflare scheduled Workflows guaranteed to run at the exact time? No. Cloudflare publishes no timing guarantee and schedules work on underutilized capacity, so a run can land late. Treat the schedule as approximate and give any monitor a wide grace period.
What happens if a step keeps failing? Each step is retried up to its configured ceiling, and once that is exhausted the instance fails. A step failing every retry for days looks the same as one that never ran unless you watch for the terminal failed state.
How long can I see the history of my scheduled Workflows? Instance state is retained for 3 days on the free plan and 30 days on paid. That is a debugging window, not an archive, so a failure that started weeks ago may have aged out before you look.
Do I still need external monitoring if Workflows has built-in retries? Yes. Retries and the dashboard cover runs Cloudflare started, but nothing alerts you when a scheduled instance is never created or a step stays failed. An external check-in on success, tied to a schedule, catches the absence of a run.
Further reading
- Durable Job Queues Are Replacing Cron: New Failure Modes
- Why Your GitHub Actions Scheduled Workflows Run Hours Late
- Dead Man's Switch Monitoring: The Only Reliable Way to Watch Cron Jobs
Conclusion: Attaching cron schedules to a Cloudflare Workflow retires the failure modes that come from owning a host and hands you durable, retryable jobs. What it does not hand you is a way to know when a run silently did not happen. Opportunistic timing, short retention, and retries that mask a fault all fail the quiet way cron always did. Watch for the absence of success from outside the platform and the move is a clear upgrade.
Sources: Cloudflare changelog: schedule Workflow instances from your Workflow binding (June 2, 2026), Cloudflare Workflows limits, Cloudflare Workers Cron Triggers.