A cron expression is five fields separated by spaces: minute, hour, day of month, month, day of week. Each is either a star meaning every value, a specific number, a list, a range, or a step.
The notation is thirty years old and almost entirely learnable in a paragraph. The one part that surprises everybody is how the two day fields interact.
What does each field accept?
The same four constructions, over different ranges.
| Field | Range | Example |
|---|---|---|
| Minute | 0–59 | */15 — every quarter hour |
| Hour | 0–23 | 9-17 — office hours |
| Day of month | 1–31 | 1 — the first |
| Month | 1–12 | 1,7 — January and July |
| Day of week | 0–7 | 1-5 — weekdays |
The weekday field allows both 0 and 7 for Sunday, which is a compatibility quirk rather than a mistake. Names work in most implementations too, so MON-FRI reads better than 1-5 and means the same thing.
A step applies to whatever precedes it, so */15 in the minute field means every fifteenth minute and 0-30/10 means minutes 0, 10, 20 and 30 only.
Why do the two day fields combine with OR?
Because that is how the original implementation behaved, and every compatible one has kept it. If both the day-of-month field and the day-of-week field are restricted, the job runs when either matches, not when both do.
So an expression asking for the 13th and for Friday runs on every 13th and on every Friday — not on Friday the 13th. The schedule people intend is far rarer than the one they write.
When one of the two is a star the ambiguity disappears, which is why nearly every correct expression leaves one of them unrestricted. Restricting both is almost always a mistake.
What happens at a clock change?
Something implementation-specific, and it is worth avoiding. A job scheduled for a time that does not exist on the spring transition may be skipped; a job scheduled in the hour that occurs twice in autumn may run twice.
Different schedulers handle this differently and none of them handles it invisibly. The reliable defence is not to schedule anything important between one and three in the morning local time.
The related trap is the zone itself. A cron entry runs in whatever zone the machine or the scheduler is configured for, and that is frequently UTC on a server and local time on a laptop — so an expression tested in one place runs at a different hour in the other.
What do the shortcuts mean?
Common schedules with names instead of fields. Daily, hourly, weekly, monthly and yearly all have keyword forms, and they run at the start of their period rather than at a time you chose.
The keyword for daily therefore means midnight, which is the busiest minute on any machine running several scheduled jobs. Spreading jobs across the hour by writing the fields out is worth doing for anything that competes for the same resource.
There is also a keyword meaning "at startup", which is not a schedule at all and behaves differently from the rest.
How many fields does yours take?
Five in the classic form, and six in several modern schedulers that add seconds at the front. Some add a year field at the end as well.
That is the most common reason a copied expression does not work: pasting a five-field expression into a six-field scheduler shifts every value one place, so a job meant to run hourly runs every minute.
Reading an expression back in plain language before deploying it catches this immediately, and it catches the day-field trap at the same time.
What does cron not do?
Anything about the job itself. It starts a command at a time and takes no interest in whether the command succeeds, how long it runs, or whether the previous run has finished.
That last one causes real trouble. A job scheduled every five minutes that occasionally takes six will start a second copy alongside the first, and by the end of a bad hour there are several running at once. Preventing it requires a lock in the job rather than a setting in the schedule.
Output is the other gap. A cron job’s output traditionally goes to mail rather than to a log, which on a modern server usually means it goes nowhere — so a job that has been failing silently for months is an ordinary discovery.
Questions people ask
Where do I check what a schedule really means? Read it back in plain language and look at the next few run times. Both catch the common mistakes in one glance.
*What does /5 in the hour field mean? Every fifth hour from zero — so 0, 5, 10, 15 and 20, not evenly spaced across the day.
Can a job run every 90 minutes? Not directly. The fields do not span hours, so it needs two entries or a different scheduler.
Does the last day of the month work? Not in the classic syntax. Some schedulers add an L for it.
Why did my job not run? Zone, the day-field trap, or the machine being asleep. In that order.
Write it and read it back. The cron expression generator builds the fields from a schedule you describe, and the cron parser turns an existing expression into plain language and the next few run times.