A cron expression is five fields. How wrong can it go? Quite wrong, as it turns out — and the failure mode is usually silent. The job runs, it just runs at the wrong time, or sixty times in a row, or never on the day you actually need it.
Here are the three patterns I've watched break production overnight, and how to spot them before they bite.
Mistake 1 — Treating */5 as "every 5 from now"
*/5 * * * * does not mean "run every 5 minutes starting from when you save this". It means "run at minutes 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 of every hour".
If you save the cron at 14:03 expecting the first run at 14:08, you will be wrong. The first run will be at 14:05.
Worse, */7 * * * * runs at minutes 0, 7, 14, 21, 28, 35, 42, 49, 56 — and then 0 again 4 minutes later, because the next hour resets the cycle. Anything that isn't a divisor of 60 will misbehave at the hour boundary.
Mistake 2 — Day-of-month AND day-of-week
The fifth field (day of week) and the third field (day of month) have a non-obvious interaction. If either is unrestricted (*), cron treats the other as the active filter. If both are specified, cron runs when either matches — an OR, not an AND.
So 0 9 1 * 1 does NOT mean "9am on Mondays that fall on the 1st". It means "9am on the 1st of every month, AND 9am every Monday". That's roughly 8 runs per month instead of zero or one.
If you want a true AND, you have to filter inside the script:
[ "$(date +\%u)" -eq 1 ] && /path/to/job.sh
Or use a cron-like scheduler that supports compound conditions natively (Quartz, for instance).
Mistake 3 — Timezone drift
Most cron daemons run in the system's local timezone, which is often UTC on cloud VMs and often local time on developer machines.
Two failure modes:
-
DST transitions. A cron set to
0 2 * * *in a region that observes daylight saving will either skip a day (spring forward) or run twice (fall back) on transition nights. - Container drift. A container's TZ defaults to UTC. If your developer laptop says BST and the production container says UTC, the same crontab runs at different wall-clock times.
The fix is to always be explicit:
# In crontab itself (Linux):
CRON_TZ=Europe/London
0 2 * * * /path/to/backup.sh
Or run everything in UTC and convert in the application layer.
A safer authoring loop
Before you commit a cron expression to production, paste it into a parser that shows you the next 10 fire times. If the times match what you expected, ship it. If they don't, you've caught the mistake before it costs you a night.
The Skojio cron helper does exactly that — paste an expression, see the next 10 fire times in your chosen timezone, plus a plain-English description of what the expression actually means. It catches all three of the mistakes above in seconds.
Recap
| Mistake | How to spot it |
|---|---|
*/N where N doesn't divide 60 |
Hour-boundary glitch |
| Day-of-month + day-of-week both set | More runs than expected |
| Implicit timezone | DST or container drift |
Cron is fine when you respect it. The above three failure modes account for nearly every "why did this run / not run" Slack thread I've seen.
Top comments (0)