January 21, 2026 · 9 min read
Cron syntax: how to read and write a cron expression
A cron expression is five fields separated by spaces. Each field is a time unit, and a job runs when the current time matches every field at once. Once you can read the fields and the four operators, any schedule is decodable.
The five fields
* * * * *
| | | | |
| | | | +--- day of week (0-7, Sun = 0 or 7, or SUN-SAT)
| | | +----- month (1-12, or JAN-DEC)
| | +------- day of month (1-31)
| +--------- hour (0-23)
+----------- minute (0-59)| Field | Allowed | Notes |
|---|---|---|
| Minute | 0–59 | Cron's finest resolution is one minute. |
| Hour | 0–23 | 24-hour clock. Midnight is 0, not 24. |
| Day of month | 1–31 | No check that the day exists in the month. |
| Month | 1–12 or JAN–DEC | Names are case-insensitive, first three letters. |
| Day of week | 0–7 or SUN–SAT | Both 0 and 7 mean Sunday. |
This is the format documented by the crontab(5) man page on Linux; the portable subset (no names, no steps) is what POSIX specifies.
The four operators
*: every value
A bare asterisk matches every value the field allows. * * * * * is “every minute of every hour of every day.”
,: a list
0 9,12,17 * * * runs at 09:00, 12:00, and 17:00. Any field takes a comma-separated list of individual values or ranges.
-: a range
0 9-17 * * * runs hourly from 09:00 through 17:00 inclusive. Ranges can be combined with lists: 0 0-6,20-23 * * *.
/: a step
A step runs every N values across a range. */15 in the minute field is 0, 15, 30, 45. 0-30/10 is 0, 10, 20, 30.
The catch: a step divides the range it is given, it does not space runs evenly around the clock. */40 in the minute field means “every 40th minute starting from 0,” so it fires at :00 and :40 and then waits 20 minutes for the next hour. If you want an even cadence that doesn't divide 60, use an explicit list.
The day-of-month / day-of-week trap
This is the single most common cron mistake. When both the day-of-month and day-of-week fields are restricted (neither is *), the job runs when either matches, not both.
0 0 1,15 * 1 # NOT "the 1st or 15th, but only if it's a Monday"
# ACTUALLY: every 1st, every 15th, AND every MondayIf you need “the first Monday of the month,” cron can't express it directly. Put a date check at the top of the script instead: [ "$(date +\\%d)" -le 07 ] || exit 0 in a job that also restricts the weekday.
The @ macros
| Macro | Equivalent |
|---|---|
@yearly / @annually | 0 0 1 1 * |
@monthly | 0 0 1 * * |
@weekly | 0 0 * * 0 |
@daily / @midnight | 0 0 * * * |
@hourly | 0 * * * * |
@reboot | once, when cron starts up |
@reboot fires when the cron daemon starts, which usually but not always coincides with a machine boot. It is not a reliable “run on every restart” and does nothing if cron restarts mid-session.
Environment lines in a crontab
A crontab can set variables that apply to the entries below them. The ones that matter:
PATH: cron's default is short (often just/usr/bin:/bin). Set it explicitly or use absolute paths for every binary.SHELL: defaults to/bin/sh. Set/bin/bashif your command uses bashisms.MAILTO: where a job's stdout/stderr is emailed. Empty (MAILTO="") disables the mail.CRON_TZ: the timezone for entries below it, on cron daemons that support it. See cron and timezones.
One escaping rule: a literal % in a command is turned into a newline by cron. Backslash-escape it (\\%); this bites people using date +%Y-%m-%d in a crontab line.
“Cron syntax” is not one standard
The five-field format above is what Unix cron, cronie, and Kubernetes CronJob use. Other schedulers extend it:
- Quartz / Spring / Jenkins add a leading seconds field (six fields) plus
?,L,W, and#for things like “last weekday” and “third Friday.” - AWS EventBridge uses six fields with a year column and requires a
?in exactly one of the day fields. - Some tools count day-of-week from Monday, or treat
7as invalid.
Always check which dialect your scheduler documents before copying an expression between systems.
Worked examples
| Expression | Runs |
|---|---|
*/5 * * * * | every 5 minutes |
0 * * * * | at the top of every hour |
0 3 * * * | every day at 03:00 |
30 2 * * 1-5 | 02:30 Monday through Friday |
0 9-17/2 * * 1-5 | 09:00, 11:00, 13:00, 15:00, 17:00 on weekdays |
0 0 1 * * | midnight on the first of every month |
0 0 * * 0 | midnight every Sunday |
15 10 1 1 * | 10:15 on 1 January |
Paste any of these into the cron expression tester to see the next dozen run times in your timezone, with daylight-saving transitions flagged.
A cron expression describes when a job should run. Whether it ran is a separate problem, and the one illari exists to solve: give it the same expression and it alerts you when a run goes missing.
Monitor a scheduled job with illari
Your job pings a URL when it runs. Miss the window and you get an alert. 25 monitors free, no credit card.