Developer

Cron expressions: reading the five fields without guessing

A schedule that should fire monthly fires every Monday as well. The two day fields are combined with OR, not AND.

4 minute readUpdated 29 September 2026

You set a job to run on the first of the month. It also runs every Monday. The expression is correct by the specification and does not mean what you intended.

The five fields

 minute  hour  day-of-month  month  day-of-week
   0-59  0-23      1-31       1-12      0-6
30 2 * * *      02:30 every day
0 9 * * 1-5     09:00 Monday to Friday
*/15 * * * *    every 15 minutes
0 0 1 * *       midnight on the 1st
0 4 * * 0       04:00 on Sunday

The operators are few: * for every value, , for a list, - for a range, and / for a step. Day-of-week counts Sunday as 0, and many implementations also accept 7 for Sunday, which is a common source of confusion when copying an expression between systems.

The two day fields

This is the trap, and it is in the original specification rather than being a bug.

If both day-of-month and day-of-week are restricted, the job runs when EITHER matches. Not both.

0 0 1 * 1     midnight on the 1st, AND every Monday

That is almost never what somebody means when they write it. If one of the two fields is *, the other behaves normally and the surprise does not arise, which is why most expressions work fine and this one case catches people.

There is no way to express "the first Monday of the month" in standard cron. You schedule it for every Monday and check the date inside the job.

Step values are not intervals

*/15 in the minute field means "every value in 0-59 divisible by 15", so 0, 15, 30, 45. That happens to be every fifteen minutes, which is why the mental model of "interval" usually works.

It stops working where the range does not divide evenly.

The same applies to */45 in minutes, which fires at 0 and 45 and then at 0 again fifteen minutes later.

Time zones and the twice-yearly failure

Cron evaluates against the server clock. If that clock observes daylight saving, two things happen every year.

In spring, the hour between 02:00 and 03:00 does not exist. A job scheduled at 02:30 does not run at all that day, on most implementations.

In autumn, that hour happens twice. A job scheduled at 02:30 may run twice.

This is the same mechanism described in Unix timestamps and the off-by-one-hour bug, and the same fix applies: run scheduled jobs on a server set to UTC. UTC has no transitions, so the problem cannot occur. If the job must fire at a local wall-clock time, that conversion belongs inside the job, where a time zone converter logic can apply the rules for the actual date.

Avoid scheduling anything between 01:00 and 03:00 local time if you cannot use UTC. It is the window where all of this happens.

The other things that go wrong

A missing trailing newline in a crontab file causes some implementations to ignore the last line entirely.

The environment is minimal. Cron does not load your shell profile, so PATH is short and your interpreter may not be found. Use absolute paths for both the command and any file it touches.

The percent sign is special in crontab and must be escaped as %, which bites anyone using date format strings in a command.

Overlapping runs. Cron does not check whether the previous run finished. A job scheduled every five minutes that takes six will accumulate processes until something falls over. Use a lock file.

Output goes to mail. Anything a job writes to stdout is mailed to the user, and on a server with no mail configured it vanishes. Redirect to a log file you can actually read.

Reading an expression you inherited

The reliable method is to go field by field, left to right, and say each one out loud. Minute, hour, day of month, month, day of week.

Then check the two questions that catch people: are both day fields restricted, and does the step value divide its range evenly? If the answer to either is yes, the expression probably does not mean what its author thought. A cron expression parser does the field-by-field reading for you and lists the next few run times, which is the quickest way to spot both problems.

And for anything that runs infrequently, check the timestamps in your job logs against what you expected rather than trusting the expression. A monthly job has eleven chances a year to be wrong before anyone notices.

Further reading

Do it hereCron Expression ParserExplain any cron expression in plain English and preview its next run timesOpen

Written for NextTool. Every statement about a tool is checked against that tool's code, and corrections are dated above. How guides are written

cronschedulingtimezonesautomation