Explainers · 7 min read · 2026-10-06
Somewhere on a server you own, a line of five numbers quietly decides when backups run, when reports are emailed and when cache is cleared. Cron has been scheduling Unix systems since the 1970s, and its expression syntax - terse, occasionally baffling, endlessly useful - has since been adopted by everything from CI pipelines to cloud functions. Learning to read it fluently takes about ten minutes and pays off for a career.
A cron expression reads left to right as: minute (0-59), hour (0-23), day of month (1-31), month (1-12), day of week (0-7, where both 0 and 7 are Sunday). The line "0 9 * * 1-5" therefore means: at minute 0 of hour 9, every day of the month, every month, but only Monday to Friday - a weekday 9 AM job. An asterisk simply means "every," so the expression is really a filter: the job fires whenever the current time matches all five fields at once.
These compose: "*/15 8-18 * * 1-5" is every fifteen minutes during business hours on weekdays. One line of punctuation has replaced a page of scheduling UI.
Day of month and day of week interact strangely: when BOTH are restricted (not asterisks), cron fires when EITHER matches, not when both do. "0 0 1 * 1" is not "first of the month that is a Monday" - it is every first of the month plus every Monday. Other classics: the missing leading zero (writing "*9" instead of "* 9" is a syntax error), steps that overshoot ("*/90" in minutes is NOT every 90 minutes - it fires at 0 and then at 0 again the next hour, because the field resets), and the fact that cron has no concept of "last day of the month" in the classic syntax, so billing jobs scheduled for the 31st simply skip short months.
Cron evaluates against the server's clock, which is usually UTC. A job written as "0 9 * * *" runs at 9 AM UTC - which is 10 AM in London for half the year and 11 AM for the other half, because daylight saving changes the offset without telling anyone. Jobs in the 1 AM and 2 AM slots can be skipped or doubled during the spring and autumn transitions. The cure is to schedule in UTC deliberately, document it, and verify with a converter before trusting any local-time assumption.
With the field order internalised - minute, hour, day of month, month, day of week - most expressions translate directly: "30 4 * * 0" is Sunday 4:30 AM (the classic maintenance window), "0 0 1 1 *" is midnight every January 1st, "*/5 * * * *" is every five minutes, and "0 0 * * 2" is weekly on Tuesday at midnight. When a schedule misbehaves, resist guessing: write out what each field literally says, and the bug almost always reveals itself as a swapped hour and minute or an OR that was meant to be an AND.
Cron is a filter, not a script: the job runs whenever the clock matches every field at once. Say that sentence out loud and half the classic bugs dissolve.