Skip to content

Cron Expression Builder & Validator

At 09:00, every Monday, Tuesday, Wednesday, Thursday, Friday
minhourdaymonthweekday
0-590-231-311-12 or jan-dec0-6 or sun-sat (0=Sun)
  1. 12026-05-29 09:00 (Fri)
  2. 22026-06-01 09:00 (Mon)
  3. 32026-06-02 09:00 (Tue)
  4. 42026-06-03 09:00 (Wed)
  5. 52026-06-04 09:00 (Thu)

Estimates for educational purposes — not financial, medical, or legal advice. See terms.

Cron syntax is one of those formats that everyone has used and almost no one has fully memorized. Five fields, each with its own range and conventions, and at least three different gotchas (day-of-month vs day-of-week semantics, named months and weekdays, the step modifier). This builder takes a cron expression, validates it, describes what it actually does in plain English, and shows the next 5 dates it will fire — so you can verify intent before pasting into a scheduler config.

The 5 fields

*   *   *   *   *
│   │   │   │   │
│   │   │   │   └── day of week     0-6 or sun-sat (0 = Sunday)
│   │   │   └────── month            1-12 or jan-dec
│   │   └────────── day of month     1-31
│   └────────────── hour             0-23
└────────────────── minute           0-59

Each field accepts:

  • * — wildcard, matches every valid value
  • 5 — specific value
  • 1-5 — range (inclusive)
  • 1,3,5 — explicit list
  • */15 — step from the field’s minimum (every 15 units)
  • 0-30/10 — step within a range

For month and day-of-week, you can use names: jan, feb, …, dec; sun, mon, …, sat. Case-insensitive.

The day-of-month vs day-of-week trap

When both day-of-month and day-of-week are restricted (neither is *), the schedule fires on a date that matches either one. Not the intersection — the union.

0 12 1,15 * 1

This fires at noon on the 1st of any month, OR the 15th of any month, OR every Monday — not just on Mondays that happen to be the 1st or 15th. To restrict to a specific day-of-week within specific dates, you need a script-side check; pure cron can’t express AND for these two fields.

When only one of them is restricted, that one is the gate. 0 9 * * 1 fires at 9 AM every Monday only.

Aliases

The standard aliases all expand to canonical 5-field expressions:

  • @yearly / @annually0 0 1 1 * (Jan 1, midnight)
  • @monthly0 0 1 * * (1st of every month, midnight)
  • @weekly0 0 * * 0 (Sundays, midnight)
  • @daily / @midnight0 0 * * * (every day, midnight)
  • @hourly0 * * * * (every hour at :00)

Example: nightly database backup

You want a backup to run every night at 2:30 AM. Try 30 2 * * *. Description: “At 02:30, every day”. Next 5 runs: tomorrow 02:30, the day after 02:30, etc. Looks right — paste into crontab.

Example: weekday business-hours health check

A health check every 15 minutes during weekday business hours (9 AM-5 PM, Mon-Fri). Try */15 9-17 * * 1-5. Description includes “every 15 minutes”, “during 09:00-17:00”, “Monday through Friday”. Next runs verify it skips weekends.

Example: monthly billing report

First of the month at 6 AM. Try 0 6 1 * *. Description: “At 06:00, on the 1st”. Next runs: 1st of next month, 1st of the following month, etc. Confirmed.

Common mistakes

Confusing day-of-week numbering. Sunday is 0 (not 7) in standard cron, though 7 is also accepted (it normalises to 0). Monday is 1. Some systems (Quartz) use 1-7 with Sunday=1 — that’s a different dialect, not standard cron.

Forgetting that */N starts at the field’s minimum. */15 in the hour field is 0, 15, 30, 45 — but the hour field’s max is 23, so this is actually 0, 15 only. To run every 6 hours, use */6, not */15. To run on a specific irregular pattern, use a list.

Expecting day-of-month and day-of-week to AND. They OR when both are restricted. To express “Mondays in January”, restrict the month: 0 9 * 1 1 — fires every Monday in January, since both being restricted gives OR. Wait, that’s still OR — so this actually fires every Monday OR every January day. To get true intersection, you need post-processing in your script.

Pasting Quartz syntax. Quartz adds a seconds field at the start: 0 0 9 * * ?. That’s 6 fields, not 5 — this tool will reject it. Use a Quartz-specific tool or remove the seconds field if you’re targeting standard cron.

What this tool does not do

It does not support Quartz extensions: L (last day of month), W (nearest weekday), # (nth weekday), ? (no specific value). Those are Quartz-only.

It does not support the optional 6th seconds field. Standard cron has 5 fields; some schedulers extend it. Stay in 5-field territory unless you specifically need the extension and your scheduler supports it.

It does not validate against your timezone. Cron schedules run in the timezone of the system that runs them — typically UTC for cloud schedulers, local time for crontab. The next-runs calculator uses your browser’s local timezone.

It does not save expressions. Use the URL bar or paste into your scheduler config to persist. For the timezone side of scheduling (converting a local “Tuesday 9am” into UTC before writing the cron), the timezone converter handles the shift.

Frequently asked questions

What's the format of a 5-field cron expression?

Five space-separated fields in order: minute (0-59), hour (0-23), day-of-month (1-31), month (1-12), day-of-week (0-6, Sunday=0). Each field accepts wildcard (*), specific number, range (1-5), step (*/2 means every 2 units), list (1,3,5), or named values for month (jan-dec) and day-of-week (sun-sat). The day-of-month + day-of-week combination has special semantics — see the next FAQ.

How do day-of-month and day-of-week interact?

When BOTH are restricted (neither is *), a date matches if EITHER matches — the OR rule. So '0 12 1,15 * 1' fires at noon on the 1st OR the 15th OR every Monday — not just on Mondays that fall on the 1st or 15th. When only one is restricted, that one is the gate. This is the Vixie cron convention used by virtually every Unix cron, but it surprises new users who expect AND semantics.

Why doesn't this support the 6-field (seconds) Quartz format?

Quartz cron (used by some Java schedulers and AWS EventBridge) adds a seconds field at the start and a year field at the end, plus extensions like 'L' (last day of month) and '#' (nth weekday). It's a different dialect. This tool implements standard 5-field Vixie cron, which is what crontab, GitHub Actions, GitLab CI, Vercel cron, and most Linux schedulers use. If you need Quartz, check the Quartz docs — the syntax overlap is partial, not complete.

What does '*/5' mean? Is that 'every 5 minutes' or 'minutes 0, 5, 10...'?

Both — they're the same thing. '*/5' in the minute field means: starting from the field's minimum (0), step by 5. So minutes 0, 5, 10, 15, ..., 55 — every 5 minutes. The same logic applies to other fields: '*/6' in the hour field is hours 0, 6, 12, 18 (every 6 hours starting at midnight). Steps in restricted ranges work too: '0-30/10' means minutes 0, 10, 20, 30 — every 10 minutes within the first half hour.

Why does my @yearly schedule show no next runs?

Common cause: you typed a date that doesn't exist, like Feb 30 ('0 0 30 2 *'). The next-runs calculator searches up to 4 years ahead — if no valid date matches, it returns nothing and shows a warning. Other impossible cases: Feb 29 outside leap years, day 31 in a 30-day month with no fallback. The calculator deliberately doesn't 'fix' these for you — silent date adjustment is worse than visible failure when scheduling things.