← Blog

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

field order
* * * * *
| | | | |
| | | | +--- 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)
FieldAllowedNotes
Minute0–59Cron's finest resolution is one minute.
Hour0–2324-hour clock. Midnight is 0, not 24.
Day of month1–31No check that the day exists in the month.
Month1–12 or JAN–DECNames are case-insensitive, first three letters.
Day of week0–7 or SUN–SATBoth 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.

not what it looks like
0 0 1,15 * 1   # NOT "the 1st or 15th, but only if it's a Monday"
               # ACTUALLY: every 1st, every 15th, AND every Monday

If 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

MacroEquivalent
@yearly / @annually0 0 1 1 *
@monthly0 0 1 * *
@weekly0 0 * * 0
@daily / @midnight0 0 * * *
@hourly0 * * * *
@rebootonce, 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/bash if 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 7 as invalid.

Always check which dialect your scheduler documents before copying an expression between systems.

Worked examples

ExpressionRuns
*/5 * * * *every 5 minutes
0 * * * *at the top of every hour
0 3 * * *every day at 03:00
30 2 * * 1-502:30 Monday through Friday
0 9-17/2 * * 1-509:00, 11:00, 13:00, 15:00, 17:00 on weekdays
0 0 1 * *midnight on the first of every month
0 0 * * 0midnight 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.