Published

How to read and write a cron expression

monitor showing Java programming
Photo by Ilya Pavlov on Unsplash

Cron expressions are terse, and most people copy one from a search result and hope. They are worth learning properly, because a schedule that is wrong by one field runs at the wrong time silently, and you find out when the backup did not happen.

The five fields

A standard expression is five fields separated by spaces, read left to right:

minute hour day-of-month month day-of-week

Each field is a number, a range or a list, and * means "every". So 0 9 * * 1 is minute 0, hour 9, any day of month, any month, day-of-week 1: nine o'clock every Monday.

The symbols that make it useful:

Symbol Meaning Example
* every value * * * * * runs every minute
, a list 0 9,17 * * * at 09:00 and 17:00
- a range 0 9 * * 1-5 at 09:00 Monday to Friday
/ a step */15 * * * * every 15 minutes

Combine them freely: 30 8 1,15 * * is 08:30 on the 1st and 15th of every month.

The traps

Day-of-month and day-of-week are ORed, not ANDed. 0 9 13 * 5 does not mean "Friday the 13th". It means the 13th of every month and every Friday. If you need both conditions, schedule one and check the other inside the script.

Sunday is 0 and 7. Most implementations accept both, but not all. Use 0 or the name SUN if your cron supports names.

Steps start from the range start, not from the current time. */10 in the minute field fires at :00, :10, :20 and so on, not "every ten minutes from when the job was installed".

Month-end does not exist. There is no expression for "last day of the month". Run on the 28th, or run daily and let the script check whether tomorrow is the 1st.

The cron expression builder turns an expression into plain English and lists the next several run times, which is the fastest way to catch all of the above before deploying it.

Time zones will get you

Cron runs in the server's local time zone, which for most cloud machines is UTC. 0 9 * * * on a UTC server runs at 09:00 UTC, which is 10:00 in London during summer and 09:00 in winter, so your "9am report" moves by an hour twice a year. Either set the job in UTC and accept the drift, or set the container's TZ variable (or CRON_TZ in crontabs that support it) to the zone you actually mean.

The time zone converter shows what a given UTC time is in your zone on a specific date, daylight saving included. When logs give you a Unix timestamp instead of a readable time, the epoch converter turns it back into a date so you can confirm the job really fired when you think it did.

Business days are not cron's job

1-5 in the day-of-week field gives you weekdays, but cron knows nothing about public holidays. If a job must only run on working days, run it on weekdays and have the script exit early on holidays. The working days calculator is useful for checking how many runs to expect in a month when you are reconciling.

The short version

Write the five fields, paste the expression into the builder, and read the next run times out loud. If they are not the times you meant, fix the expression now rather than at 3am.

More from the blog

← All posts