Skip to content
← All guidesGUIDE · UPDATED 2026-09-24

Cron expressions: reading and building schedules

The five fields, the day-of-month and day-of-week quirk, and how to check a schedule before it runs in production.

A cron schedule looks cryptic until you know that it is simply five fields read from left to right. Most production incidents caused by “the job did not run” come from one of four mistakes: the wrong time zone, the day-of-month and day-of-week rule, a schedule that is valid but rarely or never matches, or a job that is not safe to run twice. This guide covers all four, with examples you can check in the preview tools.

The five fields

┌──────── minute        (0–59)
│ ┌────── hour          (0–23)
│ │ ┌──── day of month  (1–31)
│ │ │ ┌── month         (1–12)
│ │ │ │ ┌ day of week   (0–6, Sunday = 0)
│ │ │ │ │
* * * * *

Each field accepts:

  • *: every value.
  • A single value: 5.
  • A list: 1,15.
  • An inclusive range: 9-17.
  • A step: */15 (every 15th value), or 0-30/10 (0, 10, 20, 30).

Common schedules

ExpressionRuns
*/15 * * * *Every 15 minutes
0 * * * *At the start of every hour
0 9 * * 1-509:00 on weekdays
*/15 9-17 * * 1-5Every 15 minutes from 09:00 to 17:45, Monday to Friday
30 2 * * 002:30 every Sunday
0 0 1 * *Midnight on the first of every month
0 6 1 1,4,7,10 *06:00 on the first day of each quarter

The cron expression builder assembles a schedule field by field and lists exactly which values each field matches. That is useful for catching a range such as 9-17 that includes 17:00–17:59 when you meant to stop at 17:00.

The day-of-month and day-of-week quirk

When both day fields are restricted, standard cron runs when either matches, not when both do. So 0 0 1 * 1 runs at midnight on the first of every month and on every Monday. After 2026-09-24, the cron next-run preview lists 2026-09-28 (a Monday), 2026-10-01 (a Thursday, the 1st), 2026-10-05, 2026-10-12, and 2026-10-19. If either field is *, only the other one applies. If you need “the first Monday of the month”, schedule the first seven days (0 0 1-7 * *) and check in the job whether it is Monday, or use a scheduler with an explicit extension for it.

Schedules that rarely or never run

  • 0 0 31 * * runs only in the seven months that have 31 days.
  • 0 0 29 2 * runs only in leap years.
  • 0 0 31 2 * never runs. The preview searches five years ahead and reports that no run was found.

For “the last day of the month”, standard cron has no syntax. Run on days 28–31 and check in the job whether tomorrow is the 1st, or use a scheduler that supports the L extension.

Time zones and daylight saving

Classic cron uses the server’s time zone. Around a daylight saving change, a local time between about 01:00 and 03:00 can happen twice or not at all, so jobs scheduled then run twice or are skipped. Running schedules in UTC removes the ambiguity. Most cloud schedulers, including GitHub Actions, Cloudflare Workers cron triggers, and many managed job services, use UTC anyway. The builder and preview tools on this site work in UTC for that reason. Convert your intended local time to UTC, and remember the conversion shifts by an hour when your local DST changes.

Dialects

The five-field format is supported everywhere, but extensions vary: Quartz and Spring add a seconds field and ?, L, W, and #. Many systems accept names like MON or JAN and macros like @daily. Some number weekdays 1–7. The tools here deliberately support the numeric five-field core, which works on every platform. Check your scheduler’s documentation before using anything else.

A pre-deployment checklist

  1. Build the expression and read the matching values for each field.
  2. Preview the next runs and confirm the weekdays and dates.
  3. Decide explicitly whether the schedule is in UTC or local time, and write that down next to it.
  4. Make the job idempotent, so a duplicate run around a clock change or a retry after a failure does no harm.
  5. Add monitoring for jobs that fail to run, not just jobs that fail. A silent schedule is the hardest bug to notice.