How to read a cron expression

The five cron fields explained, with ranges, steps, lists, names and shortcuts, plus the day-of-month and day-of-week rule that catches everyone out.

A cron expression is five values separated by spaces, read left to right as minute, hour, day of month, month, day of week. The job runs when the current time matches every field.

30 4 * * 1
│  │ │ │ │
│  │ │ │ └── day of week (1 = Monday)
│  │ │ └──── month (any)
│  │ └────── day of month (any)
│  └──────── hour (4am)
└─────────── minute (30)

That one reads "at 04:30 every Monday".

The five fields

#FieldAllowed valuesNotes
1Minute0 to 59
2Hour0 to 2324 hour clock, 0 is midnight
3Day of month1 to 31
4Month1 to 12, or JAN to DEC
5Day of week0 to 6, or SUN to SAT0 is Sunday, and most cron implementations also accept 7

Some schedulers use a different shape. Quartz, used by a lot of Java software, puts seconds first and adds an optional year, and numbers its weekdays 1 to 7 starting at Sunday. If an expression has six or seven fields, check which system it was written for before trusting your reading of it.

The operators

There are four, and they combine within a single field.

SymbolNameExampleMeaning
*Any* * * * *Every minute of every day
,List0 9,13,17 * * *At 09:00, 13:00 and 17:00
-Range0 9-17 * * *Hourly from 09:00 to 17:00 inclusive
/Step*/15 * * * *At :00, :15, :30 and :45

A step is always applied to a range. */15 in the minute field means "starting at 0, take every 15th value up to 59". You can step an explicit range too: 5-30/10 gives 5, 15 and 25, then stops because 35 is past the end.

Steps do not mean "every 15 minutes from now"; they select fixed values off the clock. */40 fires at :00 and :40, then waits only 20 minutes for the next hour's :00, because the count restarts each hour. Lists can contain ranges and steps, so 0 0-6/2,12,18-23 * * * is a valid hour field.

Names and shortcuts

The month and day of week fields accept three letter names, and case does not matter, so JAN, jan and Jan are the same. The classic crontab documentation says ranges and lists of names are not allowed, though many modern implementations do accept MON-FRI. If you are not certain what runs your job, use numbers: 1-5 is unambiguous everywhere.

Several shortcuts replace the whole five field expression:

ShortcutEquivalentRuns
@yearly or @annually0 0 1 1 *Midnight, 1 January
@monthly0 0 1 * *Midnight, 1st of the month
@weekly0 0 * * 0Midnight on Sunday
@daily or @midnight0 0 * * *Midnight daily
@hourly0 * * * *On the hour

@reboot also exists, but it is not a schedule: it runs the job once when cron itself starts.

The day of month and day of week trap

This is the one that bites. When both the day of month field and the day of week field are restricted, meaning neither is *, cron runs the job when either matches, not when both do.

0 0 13 * 5

You might read that as "midnight on Friday the 13th". It actually means "midnight on the 13th of every month, and also midnight every Friday": more than sixty runs a year instead of one or two.

The AND behaviour you expected applies only when one of the two fields is *. So 0 0 * * 5 is every Friday and 0 0 13 * * is every 13th, both perfectly normal. It is constraining both that triggers the OR.

There is no standard way to express "Friday the 13th" in five fields. The usual workaround is to schedule every 13th and have the job check the weekday before doing any work.

A related subtlety: some implementations decide whether a field is restricted by checking whether it literally begins with *. Under those, 0-6 in the day of week field counts as restricted even though it covers every day, quietly switching on the OR behaviour. Use * when you mean "any".

Mistakes worth checking for

  • * */2 * * * runs every minute during every second hour: 720 runs a day. You wanted 0 */2 * * *, which is 12.
  • A missing minute field shifts everything left by one and usually still parses, giving you a valid schedule at completely the wrong time.
  • 0 0 31 * * silently skips the months that have no 31st.
  • Cron uses the system or user time zone unless the scheduler lets you set one. When clocks shift for daylight saving, a job in the skipped hour may not run at all and one in the repeated hour may run twice, depending on the implementation. Jobs that must run once a day are safest scheduled away from the small hours where the shift lands.

When in doubt, read the expression back as a sentence, then check the next few times it would actually fire. If those dates are not what you described, the expression is wrong, not your reading of it.