Five numbers and some asterisks decide when your backup runs. Cron syntax is compact to the point of being cryptic, and most of it yields to five minutes of explanation — except one rule, which is genuinely counter-intuitive, has been preserved deliberately since the 1970s, and quietly makes jobs run far more often than intended.
The five fields
A standard cron expression is five space-separated fields, always in this order:
┌─ minute 0–59
│ ┌─ hour 0–23
│ │ ┌─ day of month 1–31
│ │ │ ┌─ month 1–12
│ │ │ │ ┌─ day of week 0–6 (Sunday = 0)
│ │ │ │ │
* * * * * An asterisk means "every value". So 0 9 * * * is nine in the morning
every day, and 30 2 1 * * is 2:30am on the first of each month.
Ranges (1-5), lists (1,15) and steps
(*/5) compose from there.
The OR trap
Here is the rule that catches everyone. Take 0 0 13 * 5 — midnight,
on the 13th, on a Friday. It reads like Friday the 13th. It is not. It fires at
midnight on every 13th of the month and on every Friday: about
sixty times a year instead of once or twice.
When both the day-of-month and day-of-week fields are restricted, cron joins them with or, not and — unlike every other pair of fields, which combine with and. Restrict the hour and the month and you get the intersection. Restrict the two day fields and you get the union.
This is not a bug and it will never be fixed: it is the documented behaviour of
the original Unix cron, and changing it would silently alter the schedule of
decades of working jobs. The practical rule is simple — leave one of the
two day fields as * unless you genuinely want the union. Our
validator warns whenever both are set, because there is no way to tell from the
expression alone whether you meant it.
There is no way to express "Friday the 13th" in standard cron at all. The usual
answer is to schedule 0 0 13 * * and check the weekday in the job
itself.
What the step syntax really counts
*/5 in the minute field does not mean "every five minutes from
whenever this starts". It means every minute value divisible by five: 0, 5, 10, up
to 55 — then the hour rolls over and it starts again at 0.
With a divisor of 60 that distinction never shows. With one that does not divide
evenly it does: */7 fires at 0, 7, 14, 21, 28, 35, 42, 49 and 56, and
then the next run is 0 in the following hour — four minutes later, not seven. If a
job genuinely must run at a fixed interval, cron is the wrong tool; a scheduler
that measures from the last completion is what you want.
The timezone you did not choose
Cron expressions carry no timezone. The schedule runs in whatever zone the machine is set to, and servers are very often UTC while the person writing the schedule is not. A "nightly at 2am" job written in Kolkata runs at 7:30am UTC-time on a UTC box — which is the middle of the working day for its users.
Daylight saving makes it worse. In a zone that observes it, the hour between 2 and 3am happens twice in autumn and not at all in spring, so a 2:30am job can run twice or be skipped entirely. Scheduling outside that window, or configuring the scheduler in UTC and doing the conversion yourself, avoids the whole category.
Five fields or six
One last portability trap: some systems put a seconds field first. Quartz, Spring and a number of JavaScript libraries take six fields; Unix cron takes five. Paste a five-field expression into a six-field parser and every field shifts one place — your minutes are read as seconds — so a job meant for daily can end up running once a minute. Always check which dialect the scheduler speaks before copying an expression between systems.
FAQ
Why does 0 0 13 * 5 run more often than expected?
Because when both the day-of-month and day-of-week fields are restricted, cron joins them with OR rather than AND. That expression runs at midnight on the 13th of every month and on every Friday, not only on Friday the 13th. The rule is a documented quirk of the original Unix cron and has been preserved for compatibility ever since. To restrict by only one of the two, leave the other as an asterisk.
What does */5 actually mean in a cron expression?
It means every value in that field divisible by five, counting from the start of the field range — not every five minutes from now. In the minute field it fires at 0, 5, 10 and so on through 55, then restarts at 0 in the next hour. This matters for step values that do not divide evenly: */7 in the minute field fires at 0, 7, 14, 21, 28, 35, 42, 49 and 56, so the gap between the last run of one hour and the first of the next is four minutes, not seven.
What timezone does a cron job run in?
Whatever timezone the machine or scheduler is configured for, which is very often UTC on a server even when the person writing the schedule is not. Cron expressions carry no timezone of their own. Managed schedulers such as Kubernetes CronJobs and cloud task services usually let you set one explicitly, and doing so is worth it — a job written for 9am local time silently runs at a different hour once daylight saving shifts.
Do cron expressions have five fields or six?
Standard Unix cron uses five: minute, hour, day of month, month, and day of week. Several other systems add a sixth leading field for seconds, including Quartz, Spring and many JavaScript libraries. A five-field expression pasted into a six-field parser is misread — every field shifts by one position, so what you meant as minutes is read as seconds. Check which dialect your scheduler speaks before copying an expression between systems.
Related guides
- Unix timestamps, explained — the other place a timezone assumption quietly changes what your code does.
- Regex, and why it sometimes hangs — another compact syntax with one rule that surprises everybody.
Try it now: Free Cron Expression Validator