You already know cron has five fields. What bites experienced developers is everything the five-fields tutorial skips: the Sunday ambiguity, the */5 vs 0/5 divergence, and what happens when a schedule meets daylight saving.
Every cron guide opens the same way: "cron has five fields — minute, hour, day of month, month, day of week." You know this. You've known it for years. And you can still get paged at 3 a.m. because a job that was supposed to run every five minutes ran every minute, or a nightly task silently skipped a day in March. The five fields aren't the hard part. The hard part is a short list of behaviours that are consistent with the spec, absent from the tutorial, and responsible for a wildly disproportionate share of scheduling bugs.
The fields, in one breath
Minute (0–59), hour (0–23), day of month (1–31), month (1–12), day of week (0–7). A * means "every." A list is comma-separated, a range uses a hyphen, a step uses a slash. That's the 30-second version, and it's genuinely all you need for 0 9 * * 1 (9 a.m. every Monday). Now the parts that actually trip people.
1. The Sunday problem
In the day-of-week field, both 0 and 7 mean Sunday on most implementations. Wikipedia states it plainly:
"Day of the week: 0–6 (Sunday to Saturday); 7 is also accepted for Sunday on some systems."
— Wikipedia, "Cron" (CC BY-SA 4.0)
"On some systems" is the whole warning. Vixie cron accepts both; other schedulers accept only 0; a few reject 7 outright. If you copy an expression using 7 from one host to another, it can silently mean "never" on the destination. When you want Sunday, 0 is the portable choice.
2. */5 vs 0/5 vs 5/5
In POSIX cron, */5 in the minute field means "every 5 minutes starting at 0" — so :00, :05, :10, and so on. Fine. But AWS EventBridge and some Quartz-style schedulers use a different notation where 0/5 means the same thing and 5/5 means "every 5 minutes starting at minute 5" — :05, :10, :15. These look interchangeable in a code review. They aren't. Paste a Quartz 0/5 into a POSIX crontab and depending on the parser you either get an error or, worse, a silent misfire. Always confirm which dialect the target scheduler speaks before trusting a step expression you didn't write there.
3. Daylight saving collisions
Schedule a job for 30 2 * * * — 02:30 every day — on a host observing European time, and twice a year that instant does something strange. On the spring-forward night, 02:30 doesn't exist; the clock jumps from 02:00 to 03:00. On the autumn night it exists twice. What cron does at that boundary is implementation-dependent and almost never documented where you'd look for it: some skip the job, some run it late, some run it twice. The only reliable fix is to not schedule anything between roughly 01:00 and 03:00 local time on a machine that observes DST. Move the job to 00:30 or run the whole host on UTC.
4. The timezone you didn't set
System cron runs in the server's timezone, not yours and not your users'. A 0 9 * * 1 "Monday 9 a.m." job on a UTC server fires at 10 a.m. or 11 a.m. wherever the business actually is, and shifts by an hour when local DST changes but the server's UTC clock doesn't. This is the single most common cron surprise, and it has one clean answer: run cron in UTC and do the timezone math deliberately when you define human-meaningful schedules. Convert the intended local moment to UTC once, write that into the expression, and leave a comment saying what local time it represents. A timestamp converter turns "09:00 Monday in Bratislava" into the UTC hour to actually put in the field.
5. The day-of-month / day-of-week trap
Here's the one that reads backwards. In 0 9 1 * 1, most people see "9 a.m. on Mondays that fall on the 1st." What it actually means on standard cron is 9 a.m. on the 1st of the month and 9 a.m. every Monday — the two day fields are OR'd, not AND'd, whenever both are restricted. So that expression fires far more often than intended. If you need "the 1st only when it's a Monday," cron can't express it directly; you gate it inside the job with a date check. Getting this wrong means a report that was meant to run monthly quietly runs weekly, and nobody notices until someone reconciles the numbers.
Build it, then verify what it actually does
The reliable workflow is two steps, not one. Assemble the expression with the cron builder so the field structure is correct — then paste the result into the cron parser to read back the next several fire times in plain language. The builder tells you the syntax is valid. The parser tells you whether "valid" means what you meant. Those are different questions, and the gap between them is where the 3 a.m. page comes from.
Cron is a small language with a long tail of sharp edges. Learn the five fields once; learn these five edge cases and you'll skip most of the incidents that make people quietly resent it.
← All articles