Cron syntax explained: reading the five fields without a cheat sheet
Cron expressions like 0 3 * * 1 pack a schedule into five fields. How to read them, the day-of-month vs day-of-week trap, and the mistake that fires 60 times.
Published
0 3 * * 1 runs at 3:00 every Monday. */15 * * * * runs every quarter
hour. Cron’s five-field syntax has scheduled the world’s background jobs
since the 1970s, and it rewards ten minutes of actually learning it —
partly because the notation is genuinely compact, and partly because its
two or three classic traps all look right until a job fires 60 times in
a row or silently never runs.
The five fields
Read left to right, each field answers one question:
┌───────── minute (0–59)
│ ┌─────── hour (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌─── month (1–12)
│ │ │ │ ┌─ day of week (0–6, Sunday = 0)
│ │ │ │ │
0 3 * * 1
* means “every”. Beyond single values, each field takes:
- Lists:
0,30— on the hour and half hour. - Ranges:
9-17— nine through seventeen inclusive. - Steps:
*/15— every 15th value;9-17/2— every 2 hours between 9 and 17.
So 30 8 1 * * is 08:30 on the first of each month, and
0 9-17 * * 1-5 is on the hour, business hours, weekdays.
Trap one: the asterisk you forgot
* 3 * * * does not run “at 3am”. It runs at every minute of the 3am
hour — sixty executions. The minute field must be pinned (0 3 * * *)
for a once-per-hour-or-less schedule. This is the most common cron
mistake in the wild, usually discovered via sixty identical emails.
The reverse mistake also exists: 0 */1 * * * and 0 * * * * are the
same schedule (hourly on the hour), but */60 * * * * is not a valid way
to say it — steps beyond the field’s range behave inconsistently across
implementations.
Trap two: day-of-month OR day-of-week
The odd one out in cron’s logic: when both day fields are restricted,
standard cron runs the job when either matches, not both.
0 0 13 * 5 — 13th and Friday — fires on every 13th and every Friday,
not just Friday the 13th. Every other field pair combines with AND; this
one is OR, for historical reasons, and it surprises everyone once.
(Quartz and some modern schedulers changed this behaviour, which means
the same expression can schedule differently on different systems —
worth checking when moving jobs between platforms.)
Trap three: whose midnight?
0 0 * * * runs at midnight — somewhere. Cron evaluates times in the
system’s time zone, so the same crontab means different moments on a
UTC server versus a local-time laptop, and a job scheduled at 02:30
system-local time can be skipped or run twice on DST switch nights,
since that wall-clock time does not always exist.
Servers pinned to UTC avoid the whole class of problem, at the price of
doing the zone arithmetic yourself when “3am local” is what you really
meant.
Dialects and extensions
The five-field form is the portable core, but you will meet variants:
- Names:
MON,JANwork in most modern crons (0 3 * * MON). - Six or seven fields: Quartz (Java) prepends seconds and appends an optional year — a Quartz expression pasted into Unix cron is invalid or, worse, valid-but-different.
- Macros:
@daily,@hourly,@weekly,@reboot— readable shorthands, with@reboot(run at startup) having no five-field equivalent at all. - Sunday: both 0 and 7 mean Sunday in most implementations, a mercy for the off-by-one prone.
Because “valid cron” therefore depends on which cron, the reliable
check is translation back to English: the
cron expression tool parses an expression, describes
the schedule in words, and lists the next runs — which catches
every-minute accidents and OR-logic surprises before they reach a server.
(While building it we hit the month-index bug ourselves: month numbers
are 1-based, arrays are 0-based, and 0 0 1 1 * briefly described
itself as running in February. Translate, then trust.)
A reading habit that prevents most errors
Read any expression aloud right-to-left — “on Mondays, in any month, on any day-of-month, at hour 3, at minute 0” — then ask three questions: Is the minute pinned? Are both day fields restricted (OR alert)? Whose time zone? Those three cover nearly everything that goes wrong between a crontab and reality.