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
What it means
Next runs
Syntax reference
| * | Every value |
| 5 | Exactly 5 |
| 1,3,5 | A list — 1, 3 and 5 |
| 1-5 | A range — 1 through 5 |
| */15 | Every 15th value from the start |
| 9-17/2 | Every 2nd value from 9 to 17 |
| MON-FRI | Names 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
- Type an expression, or start from a preset and adjust it.
- Read the next run times — those are computed, not described.
- Take the amber warnings seriously; they catch the mistakes that are hard to see.
- 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.