Online Tool Store Online Tool Store

Cron Expression Builder

Builds and checks five-field cron expressions by computing the actual next run times. Warns when the two day fields are OR-combined, detects schedules that never fire such as 31 February, and explains uneven step intervals.

🔒 This tool runs entirely in your browser. Your files are never uploaded to a server.

Minute

0–59

Hour

0–23

Day of month

1–31

Month

1–12 or JAN–DEC

Day of week

0–7 or SUN–SAT

Presets:

What it means

Next runs

Syntax reference
*Every value
5Exactly 5
1,3,5A list — 1, 3 and 5
1-5A range — 1 through 5
*/15Every 15th value from the start
9-17/2Every 2nd value from 9 to 17
MON-FRINames work in the last two fields

Both 0 and 7 mean Sunday. This tool handles the classic five-field syntax — extensions such as @daily, L, W, # and a seconds field are specific to particular schedulers and are not parsed here.

Evaluated in this page; nothing is uploaded. Times are computed in UTC by default because that is what a server's crontab usually runs in — but a real cron runs in the machine's timezone, so check what yours is set to before trusting a schedule around a daylight-saving change.

How to use it

  1. Type an expression, or start from a preset and adjust it.
  2. Read the next run times — those are computed, not described.
  3. Take the amber warnings seriously; they catch the mistakes that are hard to see.
  4. Copy the expression into your crontab.

The rule that catches everyone

Cron has one genuinely counterintuitive behaviour, and it is responsible for most schedules that fire when nobody expected them to. When both the day-of-month and day-of-week fields are restricted, they are combined with OR rather than AND.

That means adding a restriction can increase how often a job runs. Compare these two, evaluated from 17 August 2026:

0 0 * * 1   Aug 24, Aug 31, Sep 7, Sep 14   Mondays
0 0 1 * 1   Aug 24, Aug 31, Sep 1, Sep 7    Mondays or the 1st

The second expression looks more specific and is less. September 1st 2026 is a Tuesday, and it appears because it is the 1st — the day-of-week restriction did not exclude it. Both of those run lists are computed by this tool rather than quoted from documentation, which is the point of showing actual dates instead of a sentence.

The practical consequence is that standard cron cannot express "the first Monday of the month". There is no way to intersect the two day fields. The conventional workaround is 0 0 1-7 * 1 — the first week, on a Monday — which still fires on every day from the 1st to the 7th as well, so the job has to check the date itself and exit if it is not the day you wanted.

Schedules that never happen

An expression can be perfectly valid and still describe nothing. Because this tool evaluates the schedule rather than paraphrasing it, it can tell you:

0 0 31 2 *   never fires — February has no 31st
0 0 29 2 *   fires only in leap years — next 29 Feb 2028
0 0 31 * *   skips February, April, June, September, November

The last of those is the common version of this mistake: a monthly job set for the 31st simply does not run in seven months of the year. Plain cron has no way to say "the last day of the month", so the usual answer is to run on the 1st and look backwards, or to schedule daily and let the job decide.

Reading the step syntax correctly

A slash means "every nth value from the start of the range", not "every n minutes from now". That distinction matters at the boundaries:

*/15 * * * *     minutes 0, 15, 30, 45 — every hour
*/40 * * * *     minutes 0 and 40 only — the gap from 40 to the
                  next hour's 0 is 20 minutes, not 40
5/15 * * * *     minutes 5, 20, 35, 50
0 9-17/4 * * *   09:00, 13:00, 17:00

The */40 case is worth internalising: any step that does not divide its range evenly produces an uneven schedule that resets at the range boundary. If you want a genuinely even interval, pick a step that divides 60 — 1, 2, 3, 4, 5, 6, 10, 12, 15, 20 or 30.

Timezones and the two annual hazards

Cron runs in the timezone of the machine it is on. If that zone observes daylight saving, a job scheduled for a local time near the transition will either be skipped or run twice once a year — the clock jumps forward over 02:30, or repeats it. A job at 02:30 local time is therefore not a job that runs 365 times a year.

This tool shows UTC by default for that reason, with a toggle for local time so you can see the difference. If a schedule genuinely must run exactly once a day, run it in UTC, or pick an hour nowhere near a transition.

What this deliberately does not accept

Only classic five-field syntax is parsed. Shorthands like @daily, the L and W operators, the # nth-weekday operator and a leading seconds field all belong to specific schedulers — Quartz, systemd timers, particular cron implementations — and they do not behave identically between them. Accepting them here would suggest your expression is portable when it is not.

FAQ

Why does my schedule run more often than I expected?

Almost certainly the day-field OR rule. When both day-of-month and day-of-week are restricted, cron fires when either matches — not when both do. So "0 0 1 * 1" means the 1st of the month or any Monday, which is roughly five times a month rather than the once or twice you might have had in mind. Leave one of the two fields as * if you meant to narrow the schedule.

How can I run something on the first Monday of the month?

Not with standard five-field cron, because of that same OR rule — there is no way to express the intersection of the two day fields. The usual workaround is to schedule it for the first seven days of the month on a Monday, as "0 0 1-7 * 1", and then have the job itself check the date and exit early if it is not the first Monday. Some schedulers add a # operator for exactly this, but it is not portable.

What does the "never fires" message mean?

That the expression is syntactically valid but describes a moment that does not exist. "0 0 31 2 *" asks for the 31st of February. Rather than guessing, this tool checks every day for the next five years and reports honestly when nothing matches. A related case is "0 0 29 2 *", which is valid and does fire — but only in leap years, so the next occurrence may be years away.

Why do 0 and 7 both mean Sunday?

Historical convention, and both are widely accepted. It exists so that ranges reading Monday to Sunday can be written naturally as 1-7. Ranges that wrap around the end of the week, like Friday to Monday, cannot be written as a single range — you need a list such as 5,6,0.

What timezone do the next-run times use?

UTC by default, with a toggle for your local time. Real cron uses the timezone of the machine it runs on, which is often UTC on a server but frequently is not on a developer machine or in a container. It matters most around daylight-saving transitions, where a job scheduled in a local zone can be skipped or run twice — if that would be a problem, schedule it in UTC.

Does it support @daily, L, W and seconds?

No, and deliberately. Those are extensions belonging to particular schedulers rather than parts of the classic five-field syntax, and they behave differently between implementations. Accepting them here would imply a portability that does not exist. This tool parses standard five-field cron, which is what a Unix crontab takes.

How we compare

Feature Online Tool Store Cron description sites Reading the man page
Shows real computed run times Some do
Warns when the day fields are OR'd Documented
Detects a schedule that never fires
Nothing sent to a server
Flags overlapping-run frequency
Quartz, systemd and @shorthand syntax Some
Timezone and DST-aware scheduling Explained only

The advantage of evaluating a schedule rather than describing it is that the OR rule, the impossible dates and the uneven step intervals all become visible instead of remaining a surprise in production. For scheduler-specific extensions, check that scheduler's own documentation — this covers the portable five-field syntax only.

Explore related tools

Embed this tool

Paste this on your own site — it stays free, and every file still stays in your visitor's browser, not yours or ours.