Cron Syntax Explained, With Real Examples
A cron expression is five fields separated by spaces that tell a server when to run a job: minute, hour, day-of-month, month, day-of-week. 0 9 * * 1-5 means "9:00 AM, Monday through Friday." That's the whole idea — the trick is the operators and a couple of gotchas that catch even experienced developers. Paste any expression into the free Cron Parser to see it in plain English plus its next run times. Here's how to read and write them.
On this page
The five fields
* * * * *
| | | | |
| | | | +-- day of week (0-7, where 0 and 7 = Sunday)
| | | +---- month (1-12)
| | +------ day of month (1-31)
| +-------- hour (0-23)
+---------- minute (0-59)
A handy mnemonic for the order: M H Dom M Dow — Minute, Hour, Day-of-month, Month, Day-of-week.
| Field | Range |
|---|---|
| Minute | 0–59 |
| Hour | 0–23 (24-hour clock) |
| Day of month | 1–31 |
| Month | 1–12 (or JAN–DEC) |
| Day of week | 0–7 (0 and 7 both = Sunday; or SUN–SAT) |
The operators (just four)
| Operator | Means | Example |
|---|---|---|
* |
Every value | * * * * * = every minute |
, |
A list | 0 9,17 * * * = 9:00 and 17:00 |
- |
A range | 0 9-17 * * * = every hour 9:00–17:00 |
/ |
A step | */5 * * * * = every 5 minutes |
One subtlety on steps: */5 in the minute field means minutes 0, 5, 10, 15… — it counts from zero within the field, not "5 minutes from whenever you saved it." And */35 means minutes 0 and 35 only, not once every 35 minutes continuously.
The gotcha that gets everyone: day-of-month vs day-of-week
This is the single most misunderstood thing in cron. When you put a real value (not *) in both the day-of-month field and the day-of-week field, standard cron runs the job when either matches — it's an OR, not an AND.
So 0 0 13 * 5 does not mean "Friday the 13th." It means "midnight on the 13th of every month, OR midnight every Friday" — which fires far more often than you'd expect. To schedule by weekday, leave day-of-month as * (0 9 * * 1-5); to schedule by date, leave day-of-week as * (0 9 13 * *). Only restrict one of the two.
A related trap: the first field is minute, not hour. * 9 * * * doesn't run "at 9" — the * in the minute field makes it run every minute of the 9 AM hour (60 times). For 9:00 exactly, you need 0 9 * * *.
Common expressions (copy-ready)
| Expression | When it runs |
|---|---|
* * * * * |
Every minute |
*/5 * * * * |
Every 5 minutes |
*/15 * * * * |
Every 15 minutes |
0 * * * * |
Every hour, on the hour |
0 */2 * * * |
Every 2 hours |
0 0 * * * |
Every day at midnight |
0 9 * * * |
Every day at 9:00 AM |
30 17 * * * |
Every day at 5:30 PM |
0 0,12 * * * |
Twice daily — midnight and noon |
0 9 * * 1-5 |
9:00 AM on weekdays (Mon–Fri) |
0 9 * * 1 |
9:00 AM every Monday |
0 0 * * 0 |
Midnight every Sunday |
30 3 * * 6,0 |
3:30 AM on weekends |
0 0 1 * * |
Midnight on the 1st of every month |
0 0 1 1,4,7,10 * |
Midnight on the 1st each quarter |
*/10 9-17 * * 1-5 |
Every 10 min during business hours, weekdays |
Names instead of numbers
You can use three-letter names for months and weekdays, which reads more clearly: 0 9 * * MON-FRI is the same as 0 9 * * 1-5, and 0 0 1 JAN * is the same as 0 0 1 1 *. The Cron Parser accepts both names and numbers, so use whichever you find readable.
What timezone does cron run in?
A crucial one for getting the actual run time right: a server's crontab runs in that server's local timezone — whatever the machine is configured to. So 0 9 * * * is 9 AM server time, which may not be your time. Two practical notes:
- If your server is set to UTC (most cloud servers are),
0 9 * * *is 9 AM UTC. - Some platforms are fixed to UTC regardless — GitHub Actions cron is always UTC, for example. Always confirm the timezone of wherever the job runs before trusting the clock.
A word on the "seconds" field
Standard Unix/Linux crontab has five fields and cannot do seconds — the smallest interval is one minute. Some other systems (Quartz, Spring, certain schedulers) add an optional sixth field for seconds at the front, like */30 * * * * * for every 30 seconds. The Cron Parser accepts that optional seconds field so you can read those too — but don't paste a 6-field expression into a standard 5-field crontab, or it'll misread every field and run at the wrong time. Match the field count to the system you're targeting.
Always verify before you ship
Cron mistakes are quiet — a wrong expression doesn't error, it just runs at the wrong time (or constantly). Before committing one, paste it into the Cron Parser: it gives you a plain-English description, a field-by-field breakdown, and the next several run times so you can confirm it does what you think. Seeing "next runs: Mon 09:00, Tue 09:00…" is the fastest way to catch an OR-gotcha or an off-by-one-field error before production does.