Cron Expressions Explained
A cron expression is five numbers and a handful of punctuation, and it answers exactly one question: on which minutes should this job run? Once you can read the five fields left to right and know what *, a range, a list and a step each do, almost every schedule you meet is decodable on sight. This guide walks the fields with worked examples, decodes ten real-world schedules field by field, and is honest about the one rule that trips up nearly everyone — and about where standard cron stops and vendor extensions begin.
Five fields, always in this order
A standard cron line is five space-separated fields. The order is fixed, and getting it wrong is the most common mistake — the parser will often accept a swapped pair and simply run at the wrong time. Reading left to right:
The scheduler wakes up once a minute, compares the current time against every stored expression, and runs the jobs whose fields all match. Because that check is per-minute, one minute is the finest resolution classic cron offers — there is no "every 30 seconds" in a five-field line.
The four operators: * - , /
Every field accepts the same four operators, and mastering these four is 90% of reading cron.
- Asterisk
*— every value the field allows. In the hour field it means "every hour"; in the minute field, "every minute". - Range
a-b— an inclusive span.1-5in day-of-week is Monday through Friday;9-17in the hour field is 9am through 5pm inclusive. - List
a,b,c— specific values enumerated.0,30in the minute field fires at :00 and :30. Lists and ranges combine:1-5,0is weekdays plus Sunday. - Step
*/n— every n-th value starting from the low end.*/15in the minute field is0, 15, 30, 45;*/2in the hour field is every even hour. A step can also follow a range, so0-30/10is0, 10, 20, 30— the step counts only inside the range.
These pieces mix freely within one field: 0,30,45-55/5 is a legal minute spec (0, 30, 45, 50, 55). A useful sanity check is that */15 and 0,15,30,45 describe the identical set — steps are just shorthand for the arithmetic list they expand to.
Ten real schedules, decoded field by field
Here are schedules you will actually meet, each split into its five fields so you can see how the meaning is assembled. Read every row as "minute · hour · day-of-month · month · day-of-week".
| Expression | min | hour | dom | mon | dow | Runs |
|---|---|---|---|---|---|---|
| * * * * * | * | * | * | * | * | Every minute |
| */15 * * * * | */15 | * | * | * | * | Every 15 minutes (:00, :15, :30, :45) |
| 0 * * * * | 0 | * | * | * | * | Top of every hour |
| 0 */6 * * * | 0 | */6 | * | * | * | Every 6 hours (00:00, 06:00, 12:00, 18:00) |
| 0 0 * * * | 0 | 0 | * | * | * | Every day at midnight |
| 30 2 * * * | 30 | 2 | * | * | * | Every day at 02:30 (a common backup window) |
| 0 9 * * 1-5 | 0 | 9 | * | * | 1-5 | 09:00 every weekday (Mon–Fri) |
| 0 0 * * 0 | 0 | 0 | * | * | 0 | Midnight every Sunday |
| 0 0 1 * * | 0 | 0 | 1 | * | * | Midnight on the 1st of every month |
| 15 10 1 1 * | 15 | 10 | 1 | 1 | * | 10:15 on 1 January, once a year |
Notice the pattern: to pin a schedule to a specific time you set the small fields (minute, hour) to numbers and leave the larger ones as *; to make it periodic you reach for a step or a range. 0 */6 * * * fires four times a day because the step is in the hour field and the minute is pinned to 0 — drop the 0 to * and you would get every minute of every sixth hour, 240 runs a day instead of 4.
The day-of-month / day-of-week trap
This is the rule that surprises almost everyone, and it is worth stating precisely. When both the day-of-month field and the day-of-week field are restricted — that is, neither one is * — classic Vixie cron (the implementation on most Linux systems) fires when either matches. It is an OR, not an AND.
If instead one of the two day fields is *, only the other applies, and there is no ambiguity: 0 0 * * 5 is simply "every Friday", 0 0 13 * * is simply "the 13th". The OR only bites when you constrain both at once. There is no way in pure five-field cron to express "the 15th, but only if it is a Monday" — you either accept the OR behaviour or add a one-line date guard inside the job itself (for example, exit early unless date +%u reports the weekday you want).
Timezones and daylight saving
A cron expression has no timezone baked in; it is matched against whatever clock the scheduler runs on. System cron on a Linux box uses that machine's local time, which means daylight-saving transitions can bite: a job scheduled for 30 2 * * * is skipped entirely on the spring-forward night in zones that jump from 02:00 straight to 03:00, and can run twice on the autumn night when 02:30 happens two times. Many managed and cloud schedulers avoid this by interpreting expressions in UTC instead. Because the correct reading depends entirely on where the job executes, always confirm the interpreting timezone before you trust a schedule — the same five fields mean a different wall-clock time on a UTC scheduler than on a server set to New York.
Paste a cron line and get it in plain English plus the next 8 fire times in UTC and your local timezone — live, in your browser, nothing sent anywhere.
Where standard cron stops: Quartz, @macros and @reboot
Everything above is portable five-field cron and behaves the same across Vixie cron, most cron libraries and the majority of cloud schedulers. Beyond it lies a thicket of non-standard extensions that look like cron but are not universally understood, and assuming they are is a real source of "why didn't my job run" bugs.
@macroshortcuts. Vixie cron adds named aliases —@yearly(same as0 0 1 1 *),@monthly,@weekly,@daily(0 0 * * *) and@hourly(0 * * * *). They are convenient on Linux but are not part of the original POSIX grammar, so many cloud schedulers and language libraries reject them. When in doubt, write out the five fields.@reboot. This one is genuinely different: it does not schedule against the clock at all. It runs once when cron itself starts (typically at boot), so it has no "next run time" and cannot be simulated by any calendar walk. It is for start-up tasks, not recurring ones.- Quartz and six-field cron. Java's Quartz scheduler, and some others, use a different grammar: a leading seconds field (making six or seven fields),
?to mean "no specific value" in a day field, and special tokens likeL(last day of month/week),W(nearest weekday) and5#3(the third Friday). Crucially, Quartz day-of-week is numbered 1–7 with 1 = Sunday, off by one from Unix cron's 0 = Sunday — a2means Monday in Quartz but Tuesday in Unix cron. These expressions are not interchangeable with five-field cron.
The honest summary: the five fields, the four operators and the OR rule are the stable core you can rely on anywhere. The macros and Quartz tokens fail silently when you move a schedule from a Linux crontab to a cloud scheduler or a Java service. If a schedule has to be portable, keep it to numeric five-field cron and test it against the environment that will actually run it.
Open the cron expression tester → to decode any of these lines and preview their next fire times, or browse all Polyatic tools.