Cron syntax explained: five fields, six and DST

Cron looks like five numbers and a star, until a schedule fires twice, never fires, or fires at the wrong hour once a year. Most of those surprises come from three rules: the OR between day fields, the difference between Unix and Quartz, and daylight saving.

The fields, in order

A Unix cron expression has five fields: minute, hour, day of month, month, day of week. Quartz (and Spring's scheduler) adds a seconds field at the front and an optional year at the end, so the same intention is written with six fields. Reading left to right is the whole of the basic syntax; the difficulty is in the values each field accepts and in how the two day fields interact.

FieldUnix rangeQuartz rangeNotes
Second—0–59Quartz only; Unix fires at second 0
Minute0–590–59*, lists, ranges, steps
Hour0–230–2324-hour clock
Day of month1–311–31L = last day (Quartz)
Month1–121–12Names JAN–DEC accepted
Day of week0–7 (0 or 7 = Sunday)1–7 (1 = Sunday)The numbering is the classic porting bug
Year—1970–2099 (optional)Quartz only

Stars, lists, ranges and steps

* means every value. A comma is a list (1,15), a hyphen is an inclusive range (1-5), and a slash is a step (*/15). A step may follow a range (9-17/2), and in Quartz, and in most modern Unix implementations, it may follow a single value (0/15) meaning “start there and go to the end of the field by that step”. That last form is worth checking in the scheduler you actually use: a parser that treats 0/15 as just 0 turns an every-fifteen-minutes job into an hourly one, and nothing reports an error.

The step is not a frequency cap. */15 in the minute field means minutes 0, 15, 30 and 45 of every hour; it does not mean “no more than four times an hour” in a way that interacts with other fields. Cron matches a wall-clock time against the fields; it does not schedule intervals.

The day-of-month OR day-of-week rule

In Unix cron, if both day-of-month and day-of-week are restricted (neither is *), a run happens when either matches. 0 0 1 * MON fires on the first of the month and on every Monday — an OR that catches people who expect AND. If one of the fields is *, the other governs alone.

Quartz removes the ambiguity by requiring a ? in one of the two day fields to mean “no constraint here”. 0 0 12 ? * MON is every Monday at noon; 0 0 12 1 * ? is the first of every month. A ? anywhere else is invalid. If you copy a Quartz expression into a Unix crontab, the ? is a field value Unix does not understand — and if you copy a Unix expression into Quartz, the OR rule is gone, so the meaning can change silently.

Measured next-run times

The table below is the output of the scheduler engine this site's generator uses, evaluated from 2026-10-24 00:00 UTC. The dates are UTC; in another zone the same expressions produce the same wall-clock times, shifted.

ExpressionDialectNext runs (UTC)
*/5 * * * *Unix00:05, 00:10, 00:15 on 2026-10-24
0 9 * * 1-5Unix09:00 on Mon 2026-10-26, Tue 27, Wed 28
0 0 1 * *Unix00:00 on 2026-11-01, 2026-12-01, 2027-01-01
30 2 * * *Unix02:30 daily
0 0/15 * * * ?Quartz00:15, 00:30, 00:45 on 2026-10-24
0 0 12 ? * MONQuartz12:00 on Mon 2026-10-26, 11-02, 11-09

Note the fourth row: 2026-10-24 is a Saturday, so 0 9 * * 1-5 does not run that day and the first run is Monday. The last row shows the Quartz weekday numbering — MON is unambiguous, which is exactly why names are worth using.

Daylight saving is where schedules break

A cron expression names a wall-clock time, and wall clocks move twice a year. In a zone that springs forward at 02:00, a job scheduled for 30 2 * * * has no 02:30 that day: the local time does not exist. Different implementations handle that differently — some skip the run, some run it at 03:00, some run it at 01:30 — and the same job then runs twice in the autumn, when 01:30 occurs twice. A scheduler that constructs a local time and re-checks the fields (as the engine here does) skips the nonexistent time rather than silently firing at the wrong hour.

The practical consequence is that a “daily at 02:30” job cannot be assumed to run 365 times a year. If the job must run exactly once per day regardless of the clock, schedule it in UTC — but then its local time shifts by an hour for half the year. There is no schedule that is both fixed in local time and guaranteed once per local day across a DST change; decide which property matters.

Expressions that look right and are not

  • 0 0 31 2 * — 31 February. The expression is valid and never fires. Cron does not warn about impossible calendar combinations.
  • */15 9-17 * * 1-5 — every 15 minutes from 09:00 to 17:45 on weekdays. The range is inclusive; 17:45 is inside it.
  • 0 0 * * 7 — Sunday in Unix, where 0 and 7 both mean Sunday. In Quartz, 7 means Saturday; the same number, a different day.
  • 0 0 1 * MON — first of the month OR every Monday in Unix, not both. Use Quartz with ? if you need one or the other explicitly.
  • @reboot — not an expression. It runs when the daemon starts, which may be several times a day or not for months; it has no schedule to compute.

Questions this guide raises

Does cron handle time zones?

Traditional Unix cron runs in the system time zone, and some daemons accept a CRON_TZ setting; Quartz has a time-zone property on the trigger. The expression itself carries no zone, so the same file behaves differently on servers in different regions. For anything that must align with a business day, state the zone explicitly and test across a DST boundary.

Why did my job run twice at the clock change?

In autumn the local clock repeats an hour, so a wall-clock time such as 01:30 occurs twice. A scheduler that fires on every matching wall-clock minute fires twice unless it detects the repetition. Scheduling in UTC avoids the duplicate but moves the local time.

What is the difference between 0 and 7 for Sunday?

In Unix cron both mean Sunday. In Quartz, days are numbered 1–7 with 1 = Sunday, so 7 means Saturday. This is the most common error when moving an expression between the two dialects; using day names avoids it.

Can cron run a job every N seconds?

Not in the five-field Unix form, which has no seconds field. Quartz and Spring support a seconds field, so 0/15 in seconds runs four times a minute. For sub-minute scheduling on Unix, the usual approach is a wrapper that loops or sleeps, not a cron expression.