Skip to content
ToolsNow
Guides All tools

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, JAN work 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.