Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/cron-triggers/gotchas.md
1# Cron Triggers Gotchas23## Common Errors45### "Timezone Issues"67**Problem:** Cron runs at wrong time relative to local timezone8**Cause:** All crons execute in UTC, no local timezone support9**Solution:** Convert local time to UTC manually1011**Conversion formula:** `utcHour = (localHour - utcOffset + 24) % 24`1213**Examples:**14- 9am PST (UTC-8) → `(9 - (-8) + 24) % 24 = 17` → `0 17 * * *`15- 2am EST (UTC-5) → `(2 - (-5) + 24) % 24 = 7` → `0 7 * * *`16- 6pm JST (UTC+9) → `(18 - 9 + 24) % 24 = 33 % 24 = 9` → `0 9 * * *`1718**Daylight Saving Time:** Adjust manually when DST changes, or schedule at times unaffected by DST (e.g., 2am-4am local time usually safe)1920### "Cron Not Executing"2122**Cause:** Missing `scheduled()` export, invalid syntax, propagation delay (<15min), or plan limits23**Solution:** Verify export exists, validate at crontab.guru, wait 15+ min after deploy, check plan limits2425### "Duplicate Executions"2627**Cause:** At-least-once delivery28**Solution:** Track execution IDs in KV - see idempotency pattern below2930### "Execution Failures"3132**Cause:** CPU exceeded, unhandled exceptions, network timeouts, binding errors33**Solution:** Use try-catch, AbortController timeouts, `ctx.waitUntil()` for long ops, or Workflows for heavy tasks3435### "Local Testing Not Working"3637**Problem:** `/__scheduled` endpoint returns 404 or doesn't trigger handler38**Cause:** Missing `scheduled()` export, wrangler not running, or incorrect endpoint format39**Solution:**40411. Verify `scheduled()` is exported:42```typescript43export default {44async scheduled(controller, env, ctx) {45console.log("Cron triggered");46},47};48```49502. Start dev server:51```bash52npx wrangler dev53```54553. Use correct endpoint format (URL-encode spaces as `+`):56```bash57# Correct58curl "http://localhost:8787/__scheduled?cron=*/5+*+*+*+*"5960# Wrong (will fail)61curl "http://localhost:8787/__scheduled?cron=*/5 * * * *"62```63644. Update Wrangler if outdated:65```bash66npm install -g wrangler@latest67```6869### "waitUntil() Tasks Not Completing"7071**Problem:** Background tasks in `ctx.waitUntil()` fail silently or don't execute72**Cause:** Promises rejected without error handling, or handler returns before promise settles73**Solution:** Always await or handle errors in waitUntil promises:7475```typescript76export default {77async scheduled(controller, env, ctx) {78// BAD: Silent failures79ctx.waitUntil(riskyOperation());8081// GOOD: Explicit error handling82ctx.waitUntil(83riskyOperation().catch(err => {84console.error("Background task failed:", err);85return logError(err, env);86})87);88},89};90```9192### "Idempotency Issues"9394**Problem:** At-least-once delivery causes duplicate side effects (double charges, duplicate emails)95**Cause:** No deduplication mechanism96**Solution:** Use KV to track execution IDs:9798```typescript99export default {100async scheduled(controller, env, ctx) {101const executionId = `${controller.cron}-${controller.scheduledTime}`;102const existing = await env.EXECUTIONS.get(executionId);103104if (existing) {105console.log("Already executed, skipping");106controller.noRetry();107return;108}109110await env.EXECUTIONS.put(executionId, "1", { expirationTtl: 86400 }); // 24h TTL111await performIdempotentOperation(env);112},113};114```115116### "Security Concerns"117118**Problem:** `__scheduled` endpoint exposed in production allows unauthorized cron triggering119**Cause:** Testing endpoint available in deployed Workers120**Solution:** Block `__scheduled` in production:121122```typescript123export default {124async fetch(request, env, ctx) {125const url = new URL(request.url);126127// Block __scheduled in production128if (url.pathname === "/__scheduled" && env.ENVIRONMENT === "production") {129return new Response("Not Found", { status: 404 });130}131132return handleRequest(request, env, ctx);133},134135async scheduled(controller, env, ctx) {136// Your cron logic137},138};139```140141**Also:** Use `env.API_KEY` for secrets (never hardcode)142143**Alternative:** Add middleware to verify request origin:144```typescript145export default {146async fetch(request, env, ctx) {147const url = new URL(request.url);148149if (url.pathname === "/__scheduled") {150// Check Cloudflare headers to verify internal request151const cfRay = request.headers.get("cf-ray");152if (!cfRay && env.ENVIRONMENT === "production") {153return new Response("Not Found", { status: 404 });154}155}156157return handleRequest(request, env, ctx);158},159160async scheduled(controller, env, ctx) {161// Your cron logic162},163};164```165166## Limits & Quotas167168| Limit | Free | Paid | Notes |169|-------|------|------|-------|170| Triggers per Worker | 3 | Unlimited | Maximum cron schedules per Worker |171| CPU time | 10ms | 30s (<1hr interval), 15min (≥1hr interval) | May need `ctx.waitUntil()` or Workflows |172| Execution guarantee | At-least-once | At-least-once | Duplicates possible - use idempotency |173| Propagation delay | Up to 15 minutes | Up to 15 minutes | Time for changes to take effect globally |174| Min interval | 1 minute | 1 minute | Cannot schedule more frequently |175| Cron accuracy | ±1 minute | ±1 minute | Execution may drift slightly |176177## Testing Best Practices178179**Unit tests:**180- Mock `ScheduledController`, `ExecutionContext`, and bindings181- Test each cron expression separately182- Verify `noRetry()` is called when expected183- Use Vitest with `@cloudflare/vitest-pool-workers` for realistic env184185**Integration tests:**186- Test via `/__scheduled` endpoint in dev environment187- Verify idempotency logic with duplicate `scheduledTime` values188- Test error handling and retry behavior189190**Production:** Start with long intervals (`*/30 * * * *`), monitor Cron Events for 24h, set up alerts before reducing interval191192## Resources193194- [Cron Triggers Docs](https://developers.cloudflare.com/workers/configuration/cron-triggers/)195- [Scheduled Handler API](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/)196- [Cloudflare Workflows](https://developers.cloudflare.com/workflows/)197- [Workers Limits](https://developers.cloudflare.com/workers/platform/limits/)198- [Crontab Guru](https://crontab.guru/) - Validator199- [Vitest Pool Workers](https://github.com/cloudflare/workers-sdk/tree/main/fixtures/vitest-pool-workers-examples)200