Here is a cron expression: 0 0 */2 * 1. Ask three tools how often it runs in September 2026 and you get three numbers. Real cron fires it twice. A widely used parsing library fires it seventeen times. Our own builder, until we fixed it this week, said four.
None of those tools is broken in the ordinary sense. They disagree about one rule, and the rule is stranger than its reputation.
The trap everyone knows about
Cron has two day fields. Day-of-month is the third column, day-of-week is the fifth, and the famous oddity is that when both are set, cron matches either rather than both.
So 0 9 1 * 1 does not mean "9am on Mondays that fall on the 1st". It means 9am on the 1st of the month, and also 9am on every Monday. In September 2026 that is the 1st, then the 7th, 14th, 21st and 28th.
This is documented, and Vixie cron's own source comments on it with some feeling — the code says that yes, it is bizarre, and that like many bizarre things, it is the standard.
The trap almost nobody knows about
Read the rule as "the two day fields are OR-ed" and you have it slightly wrong, in a way that only shows up on certain expressions. The actual behaviour is a two-way switch.
- If neither day field begins with a star, cron OR-s them.
- Otherwise cron AND-s them.
A bare * expands to every day, so AND-ing it changes nothing, and that is why the switch is invisible most of the time. 0 9 1 * * still fires on the 1st. 0 9 * * 1 still fires on Mondays.
⚠️ The switch becomes visible the moment you write a step. */2 begins with a star, so it flips the rule to AND — and it is also a genuine restriction, because it selects the 1st, 3rd, 5th and so on. Both halves are true at once, and that combination is what tools get wrong.
Working the example
Back to 0 0 */2 * 1: midnight, every second day-of-month, on Mondays.
September 2026 opens on a Tuesday, so its Mondays are the 7th, 14th, 21st and 28th. The */2 field selects the odd-numbered days: 1, 3, 5, 7 and on through 29.
| Rule applied | Days in September 2026 | Firings |
|---|---|---|
| Vixie: star present, so AND | 7, 21 | 2 |
| OR the two fields regardless | all Mondays and all odd days | 17 |
| Treat a star field as saying nothing | 7, 14, 21, 28 | 4 |
The middle row comes from a parser that has learned the famous OR rule and applies it unconditionally. The bottom row is subtler; it comes from treating "begins with a star" as "imposes no constraint" and dropping the field entirely. That was our bug.
The version of this that will actually bite you
That Monday example is a curiosity. What follows is the version that costs you something.
0 9 */2 * *
Every other day at nine. It is a natural thing to write, and it is the most common way people express an alternating schedule.
Under cron's rule, the day-of-month field begins with a star, so the two day fields are AND-ed: odd-numbered days AND every weekday, which reduces to odd-numbered days. Fifteen or sixteen firings a month.
⚠️ Our builder read that field as unconstrained, discarded the set it had just parsed, and reported a run every single day — roughly twice the intended schedule, under a description that said "every day" and therefore looked deliberate rather than wrong. If you had checked our summary line against your intention, the summary would have agreed with the output and both would have been wrong together.
What we changed
The fix was one line. Our parser already recorded whether each field's raw text began with a star, which is the exact flag cron uses to switch between AND and OR. But the matcher, seeing a field it considered unrestricted, was throwing the parsed set away instead of intersecting with it.
Two details were worth keeping while changing that. 1-31 covers every day of every month and is still a restriction, because cron decides from the text and not from how many days the field ends up selecting — so 0 9 1-31 * 1 OR-s to every day, which surprises people who expect it to behave like *. And the description line had the same confusion in a second place: it was answering "is this field a star?" when the sentence needed "does this field narrow anything?".
You can check any of this on our cron builder, which now shows the next run dates alongside the reading. The dates are the part to trust. A sentence describing a schedule is a translation. This kind of bug lives in the gap between what the translation says and what the machine does.
What to do with your own crontabs
Search them for a step in either day field — */ followed by a number in column three or column five. Those are the lines where the three answers diverge. Everything else behaves the way you expect.
If you find one, decide which schedule you actually meant, because the AND is frequently not it. An alternating daily job usually wants 0 9 */2 * * and gets what it wants. A job meant to run on alternating Mondays does not get that from 0 0 */2 * 1 — it gets the Mondays that happen to fall on odd dates, which is neither fortnightly nor monthly and drifts as month lengths change.
Where the schedule matters, the reliable move is to run it against a calendar rather than reason about it. Our builder prints the next dates; so does anything that implements the rule properly. What makes this class of bug durable is that a wrong schedule reads as a deliberate one.
Where this comes from, and what will date it
The AND/OR switch was read from Vixie cron's own source, which is the implementation the standard behaviour descends from and which POSIX-derived crons follow. The September 2026 dates are ordinary calendar arithmetic and you can check them against any calendar: the Mondays are the 7th, 14th, 21st and 28th, and intersecting those with the odd-numbered days leaves the 7th and the 21st.
The seventeen-firing figure is what you get by OR-ing the two sets, which is what a parser does when it applies the famous rule unconditionally. We are describing a behaviour rather than naming and shaming a package; several implementations differ here and the number depends on which one you have.
⚠️ We verified our tool against the rule as read from the source, not against a running cron daemon. The arithmetic follows from that reading. If your system's cron deviates — several do, and say so — then its own man 5 crontab is the authority, not this article.
This ages if a widely used implementation changes the switch, which would be a breaking change to schedules already in production and is therefore unlikely. It is more likely to age through the scheduler you actually use, since a lot of modern schedulers accept cron syntax without inheriting cron semantics.