DEV Community

lumm
lumm

Posted on

7 cron expression gotchas that will silently break your scheduled jobs

I've been working with cron expressions across different platforms for a while now, and I keep running into the same traps — some of them cost me hours of debugging, one of them cost me a full day of missed backups.

Here are 7 gotchas I've collected, with fixes for each.

1. "Every 6 hours" vs "every minute of hour 6"

This is the classic. You write:

0 6 * * *
Enter fullscreen mode Exit fullscreen mode

Thinking it means "every 6 hours." It doesn't. It means "at 06:00 every day." What you actually want:

0 */6 * * *
Enter fullscreen mode Exit fullscreen mode

The */6 in the hour field means "every 6th hour." Without the slash, you're just picking hour 6.

2. GitHub Actions is UTC-only and lies about timing

Your workflow says:

on:
  schedule:
    - cron: '0 9 * * 1-5'
Enter fullscreen mode Exit fullscreen mode

You think it runs at 9am your time. It runs at 9am UTC. If you're in US Eastern, that's 5am. If you're in Beijing, that's 5pm.

Worse: GitHub Actions can delay scheduled runs by 5-30 minutes under heavy load. A job scheduled for 9:00 UTC might actually fire at 9:22. This matters if you have downstream dependencies.

3. */5 doesn't work on AWS EventBridge

You write:

cron(*/5 * * * ? *)
Enter fullscreen mode Exit fullscreen mode

EventBridge rejects it. AWS uses 0/5 instead of */5:

cron(0/5 * * * ? *)
Enter fullscreen mode Exit fullscreen mode

No error message tells you this clearly. You just get a generic validation failure.

4. Day-of-month + day-of-week: OR vs AND

This one is genuinely confusing. The expression:

0 8 15 * 1
Enter fullscreen mode Exit fullscreen mode

On Linux cron: fires on the 15th of every month OR every Monday. (OR semantics)

On AWS EventBridge / Quartz: you can't even set both — one field must be ?.

If you want "the first Monday of every month" on Linux, you need a shell guard:

0 8 1-7 * 1 [ $(date +\%u) -eq 1 ] && /your/command
Enter fullscreen mode Exit fullscreen mode

5. Vercel Hobby plan: 1 cron job per day

You deploy a Vercel cron that runs every hour:

{
  "crons": [{
    "path": "/api/cleanup",
    "schedule": "0 * * * *"
  }]
}
Enter fullscreen mode Exit fullscreen mode

On the Hobby plan, this silently gets throttled to once per day. No warning in the build output. You find out when your cleanup hasn't run for 23 hours.

Pro plan minimum interval is 1 minute. Hobby is 1 per day. Period.

6. Kubernetes timeZone field needs v1.27+

You add this to your CronJob spec:

spec:
  timeZone: "America/New_York"
  schedule: "0 9 * * *"
Enter fullscreen mode Exit fullscreen mode

On clusters older than 1.27, the timeZone field is silently ignored. Your job runs in UTC. No error, no warning.

Always check your cluster version before relying on this field.

7. Quartz uses different weekday numbers

Linux cron: 0 or 7 = Sunday, 1 = Monday

Quartz: 1 = Sunday, 2 = Monday

So 0 8 ? * 2 means Monday on Quartz but Tuesday on Linux. Copy-paste between platforms and you've shifted your entire schedule by a day.


Why I built a tool for this

After hitting most of these myself, I built cronwiz.dev — type a schedule in plain English, get the cron expression for 7 platforms (Linux, AWS, K8s, GitHub Actions, Vercel, Quartz, Cloudflare Workers) with platform-specific warnings baked in.

It also works in reverse: paste a cron expression and get a human-readable explanation. Useful when you're debugging someone else's crontab at 3am.

Free, no signup. The source is on GitHub.

I'm also building a cron monitor (dead-man's-switch style — your job pings a URL on success, you get alerted if it doesn't fire on time). Waitlist is on the site if that's interesting.


What cron gotchas have bitten you? I'm always looking for more edge cases to add to the tool.

Top comments (0)