Cron expressions explained: how to read and write them
The five fields of a cron expression, the four symbols that combine them, business examples, and the mistakes that make a job run at the wrong time or on the wrong days.
By Javier Tenjo, Full stack developer
On this page
Almost every automation has a part that repeats: the sales report that arrives every Monday, the nightly backup, the order sync every fifteen minutes. All of them need someone to say when.
On Linux servers, and on many platforms that follow their lead (GitHub Actions, Kubernetes, Cloudflare Workers scheduled triggers and a good share of automation tools), that “when” is written in the same notation: a cron expression. Five space-separated fields, such as 0 8 * * 1, which read awkwardly the first time and fluently by the third.
Cron has been the Unix job scheduler since the 1970s, and its notation survived because it is compact: a single line holds “every fifteen minutes, Monday to Friday, during office hours”.
The five fields
┌───────── minute (0–59)
│ ┌─────── hour (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌─── month (1–12)
│ │ │ │ ┌─ day of week (0–7; 0 and 7 are Sunday)
│ │ │ │ │
0 8 * * 1
The example reads like this: minute 0, hour 8, any day of the month, any month, on Mondays. In other words, every Monday at 8:00.
The first trap is the order: the minute comes before the hour. 30 8 * * * is 8:30, not 30:08. It is the opposite of how we write a time, which is why it is the most common mistake when writing an expression from memory.
The four symbols
A single number in each field only schedules one fixed time. Everything else comes from four symbols:
*(asterisk): every value. In the hour field it means “at any hour”.,(comma): a list.0 9,13,17 * * *runs at 9, 13 and 17.-(hyphen): a range, both ends included.1-5in the day of week is Monday to Friday./(slash): a step.*/15in the minute field is every fifteen minutes: at 0, 15, 30 and 45.
They combine. */15 8-18 * * 1-5 is every fifteen minutes, from 8 to 18, Monday to Friday. One subtlety in that example: the 8-18 range includes the whole of hour 18, so the last run of the day is at 18:45, not 18:00.
The step counts within the field’s range, not from the previous run. That is why a step that does not divide the range evenly leaves the last interval of each cycle shorter than the others.
Examples of business jobs
| Job | Expression |
|---|---|
| Weekly report, every Monday at 8:00 | 0 8 * * 1 |
| Backup every night at 2:30 | 30 2 * * * |
| Sync orders every 15 minutes during business hours | */15 8-18 * * 1-5 |
| Invoicing reminder on the 1st of every month at 9:00 | 0 9 1 * * |
| Quarterly close, first day of each quarter at 7:00 | 0 7 1 1,4,7,10 * |
| Clean up temporary files every Sunday at midnight | 0 0 * * 0 |
There are things cron cannot express, and it is worth knowing before you try. The last day of the month is the most requested: classic cron has no way to write it. The usual workaround is to schedule the job on days 28 to 31 and have the script check whether tomorrow is the 1st before doing anything.
If you are deciding which of your company’s tasks deserve scheduling at all, what to automate first offers a way to choose.
Time zones: the mistake that repeats the most
A cron expression does not say which time zone it is in. It runs in the zone of the system reading it, and most servers and cloud platforms work in UTC.
Write 0 8 * * 1 meaning eight in the morning in Bogotá, and on a UTC server the job runs at three in the morning Bogotá time. Colombia sits at UTC−5 all year round, so the correction is fixed: for 8:00 in Bogotá you write 0 13 * * 1.
In countries with daylight saving time it gets harder, because the gap with UTC changes twice a year. A job scheduled in UTC shifts by an hour on the local clock every time the clocks change; one scheduled in local time may, on the night of the change, skip a run or run twice if it falls in the hour that is skipped or repeated.
Three habits prevent nearly all the surprises:
- Check which zone the platform uses before writing the expression. Some let you declare the zone alongside the schedule; others only accept UTC.
- Write the zone down next to the expression, in a comment or in the job’s documentation. Whoever reads it a year from now will not have to guess.
- Avoid the 1:00 to 3:00 window for critical jobs if you work in the local time of a zone with daylight saving.
Day of month and day of week together
This is the cron rule most people discover in production. When day of month and day of week are both restricted, cron runs when either matches, not when both do.
0 9 1 * 1 does not mean “the 1st, if it falls on a Monday”. It means “the 1st of every month and, on top of that, every Monday”. The job runs around five times a month instead of once every few months.
To get the intersection, leave one of the two fields as an asterisk and check the other inside the script. For “the first Monday of every month”, for example, schedule 0 9 * * 1 and have the script exit without doing anything if the day of the month is greater than 7.
Other common mistakes
- Five fields or six. Some tools use the Quartz format, which adds seconds at the front, and some cloud services add the year at the end. An expression copied from one system to another can shift every field by one position.
- Day numbering. In classic cron, 0 is Sunday and 1 is Monday. In Quartz, 1 is Sunday. The same number, one day apart.
- Wildcards your server does not understand.
L,Wand#(last day, nearest weekday, nth weekday) belong to Quartz and some variants, not to the crontab of a Linux server. - Assuming it ran. Cron does not tell you when a job fails. Log every run and set up an alert when something does not finish cleanly, or you will find out when someone asks where the report is.
- Overlapping jobs. If a sync scheduled every five minutes takes seven, the next one starts before the previous one finishes. Either prevent two from running at once or space the schedule out.
- More frequency than needed. Calling an external API every minute burns through quotas and adds nothing if the data changes hourly.
Checking yours
The cron expression generator lets you type the whole expression, fill in the five fields separately or start from a common example. On every change it describes the expression in plain English and works out the next five runs in your time and in UTC, which is the check that catches most time zone mistakes. It also warns you when you restrict both day of month and day of week, when a step does not divide its range and when the expression would never run, and it recognises six-field Quartz expressions instead of misreading them.
Cron is a five-field notation you can learn in an afternoon and use for years. What makes it treacherous is not the syntax but what you cannot see: the server’s time zone and the “either” rule between days. Checking the next run dates before signing a job off is what separates a reliable automation from one that quietly fails on a Monday.