Cron expressions, and the two rules that catch everybody
Five fields of stars and numbers look like a puzzle. Once you know that day-of-month and day-of-week are combined with OR rather than AND, and that the expression is evaluated in the scheduler timezone, most cron confusion disappears.
By Enrique M. · 7 min read · Last reviewed 2026-10-09
The five fields
A standard Unix cron expression is five whitespace-separated fields, read left to right: minute, hour, day of month, month and day of week. Each field accepts a list of alternatives separated by commas, ranges with a hyphen, steps with a slash and an asterisk meaning every value.
- `minute` — 0 to 59
- `hour` — 0 to 23
- `day of month` — 1 to 31
- `month` — 1 to 12, or JAN to DEC
- `day of week` — 0 to 6, or SUN to SAT, with 7 also meaning Sunday
The OR rule
When you restrict both the day-of-month field and the day-of-week field, standard cron fires the job whenever either of them matches. It does not wait for both.
So `0 0 13 * 5` means "the 13th of the month, and every Friday", not "the 13th of the month when it is a Friday". If you want the intersection, you need a scheduler that supports it, or you express the job in a different way.
# Every day at 03:30
30 3 * * *
# Every Monday at 09:00
0 9 * * 1
# Every 15 minutes
*/15 * * * *
# Every 15 minutes between 09:00 and 18:00 on weekdays
*/15 9-18 * * 1-5 The timezone rule
A cron expression carries no timezone. It is evaluated in whatever timezone the scheduler runs in, which on a Linux server is usually UTC and in a container is whatever the image says.
This is the single most common cause of a job that "runs at the wrong time". The cron entry is not wrong; the assumption about the timezone is. Setting the timezone explicitly in the scheduler configuration, or in the crontab itself with `CRON_TZ`, removes the ambiguity.
The same schedule in other schedulers
Quartz has seven fields, adding seconds and a year, and uses a question mark in place of a field that does not apply. AWS EventBridge and GitHub Actions each have their own dialect, and GitHub Actions runs on UTC by default.
- Unix cron: `0 9 * * 1`
- Quartz: `0 0 9 ? * MON`
- AWS EventBridge: `cron(0 9 ? * MON-FRI *)`
- GitHub Actions: `cron: "0 9 * * 1"` in UTC
What to take away
- Day-of-month and day-of-week are combined with OR, not AND.
- The expression is evaluated in the scheduler timezone, which is often UTC.
- Quartz has seven fields, AWS and GitHub have their own dialects.
- A schedule of `*/5 * * * *` is 288 executions a day — check that you meant it.