Cron Expression Explainer

A cron expression has five fields: minute, hour, day of month, month and day of week. They are compact and easy to misread, and a few rules, such as how the two day fields combine, surprise even experienced administrators.

Reading the five fields

The fields run minute (0-59), hour (0-23), day of month (1-31), month (1-12) and day of week (0-7, where both 0 and 7 are Sunday). An asterisk means every value, a comma separates a list, a dash is a range and a slash adds a step, so */15 in the minute field means 0, 15, 30 and 45.

# m   h   dom mon dow  command
  30  2   *   *   1-5  /usr/local/bin/backup

Day of month and day of week are ORed

When both day fields are restricted, cron runs the job when either matches, not both. 0 0 13 * fri fires on every 13th and on every Friday, not only on Friday the 13th. If either day field starts with an asterisk, such as */2, the two are combined with AND instead. To get a true AND, schedule the job daily and test the weekday in the command.

0 0 13 * fri   # the 13th OR any Friday

Steps that do not divide evenly

A step restarts at the start of each larger unit. */7 in the minute field runs at 0, 7, 14 and so on up to 56, then at 0 again four minutes later. Choose steps that divide 60 for minutes and 24 for hours if you need even spacing.

*/7 * * * *   # 56 then 0: a 4-minute gap

Time zones and systemd timers

Cron runs in the server's time zone, so a job at 02:30 may be skipped or run twice around a daylight-saving change. A systemd timer expresses the same schedule as OnCalendar and can run in UTC. The two day fields cannot be combined with OR in a timer, so a schedule like the one above has no direct equivalent.

OnCalendar=Mon..Fri *-*-* 09:00:00

Open the Cron expression generator

Frequently asked questions

Why did my job not run on the 31st?

Day 31 only exists in seven months, so a schedule for the 31st skips the others, and day 30 and 29 skip February. For the last day of every month, a systemd timer can say it directly with OnCalendar=*-*~01, or in cron schedule daily and have the command check that tomorrow is the 1st.

What does @reboot do?

It runs the job once each time the machine boots. Vixie cron and cronie support it, but not every cron implementation does, and a systemd timer with OnBootSec is the portable alternative.

How do I stop a slow job overlapping with the next run?

Wrap the command in flock with a lock file, or use a systemd service, which never starts a second copy of a unit that is still running.

Guides