Cron Parser & Builder

Validate a 5-field cron expression and preview the next N execution times, or build one from a frequency picker (minute/hour/day/week/month/year).

Loading…

All processing runs in your browser — no files or inputs are uploaded to a server.

The five fields and their ranges

A classic cron expression is five whitespace-separated fields. The order is minute, hour, day-of-month, month, day-of-week — left to right going from finest to coarsest grain, except for the trailing day-of-week which sits to the right of month. Each field accepts a range, a list, or an asterisk; the table below gives the bounds.

#FieldRangeNames
1Minute0–59
2Hour0–23
3Day of month1–31
4Month1–12JAN–DEC
5Day of week0–6SUN–SAT

Operators recognized in the classic dialect

OperatorMeaningExample
*Every valid value of the field.* * * * * → every minute
,List of values.0 9,12,18 * * * → 09:00, 12:00, 18:00 daily
-Inclusive range.0 9 * * 1-5 → 09:00 Mon–Fri
/Step. */n means every n; a-b/n means every n within range a-b.*/15 * * * * → :00, :15, :30, :45 every hour
nameThree-letter alias for month or day of week (case-insensitive).0 0 * JAN MON → midnight every Monday in January

Patterns you write most often

Most production cron jobs you will ever write fall into a handful of shapes. Copy one of these and adjust:

*/5 * * * *Every 5 minutes.
0 * * * *On the hour, every hour.
0 3 * * *Daily at 03:00 — a common backup window.
0 9 * * MONMonday at 09:00 — weekly stand-up reminder.
0 18 * * 1-5Weekdays at 18:00 — end-of-business cleanup.
0 0 1 * *Midnight on the 1st of every month — billing runs.
15 2 * * 0Sunday 02:15 — weekly heavy job in a quiet window.
0 0 1 1 *Midnight January 1 — annual reset.
*/10 9-17 * * 1-5Every 10 minutes during business hours, weekdays.

Cron dialects: where the rules disagree

"Cron" is not a single specification. POSIX defines the 5-field syntax above. Vixie cron (the BSD-derived implementation that became the de facto standard on Linux) adds named days, the @reboot / @daily / @hourly shortcuts, and an implicit OR between day-of-month and day-of-week when both are restricted — a divergence that surprises everyone the first time. Quartz (the Java scheduler used in Spring, Hangfire, and Quartz.NET) prepends a seconds field, appends an optional year field, and adds ? L W # for "no specific value", last day of month, nearest weekday, and nth weekday respectively. The expression 0 15 10 ? * MON-FRI is valid Quartz but invalid POSIX.

systemd timers replace cron expressions entirely with their own OnCalendar syntax (e.g. Mon..Fri 18:00). AWS EventBridge uses a 6-field variant similar to Quartz (with seconds dropped but a year field kept). Cloudflare Workers cron triggers use the classic 5-field POSIX dialect interpreted in UTC. When copy-pasting an expression between systems, confirm which dialect each side speaks — the tool above implements POSIX 5-field with AND between DOM and DOW.

Three things that will bite you

First, time zones. crond and most schedulers interpret cron expressions in the server's local time, which silently breaks twice a year at DST transitions and creates inconsistent schedules across data centers. Cloud schedulers (Cloudflare Workers, GitHub Actions, AWS EventBridge) default to UTC. Pick one explicitly and write the expression for that zone; never assume the system agrees with you.

Second, the day-of-month plus day-of-week trap. In Vixie cron, 0 0 1 * MON runs at midnight on the 1st OR every Monday — roughly six times the rate you probably wanted. In POSIX and this tool, the same expression runs only when both conditions hold (Mondays that fall on the 1st of a month, about once a year). If you need either-or behavior, write two cron lines instead of one ambiguous one.

Third, "every 5 minutes" is not what */5 always means. */5 in the hour field starts at hour 0 and steps by 5, giving 00, 05, 10, 15, 20 — not 04, 09, 14, 19. To get an offset, use the explicit list or shift the range (4-23/5). The slash always counts from the lower bound of the range, not from the current time.

How to use

Paste a 5-field cron expression into the **Parser** tab and the tool validates each field — minute (0–59), hour (0–23), day-of-month (1–31), month (1–12 or `JAN-DEC`), day-of-week (0–6 or `SUN-SAT`, Sunday is 0) — and prints the next N upcoming fire times. Supported syntax: `*` (any), ranges `a-b`, lists `a,b,c`, steps `*/n` and `a-b/n`, and case-insensitive month/weekday names. When both day-of-month and day-of-week are restricted, this parser uses **AND** (POSIX behavior) rather than Vixie cron's OR — see the FAQ for the trade-off.

The **Builder** tab lets you compose an expression from a frequency picker instead. Pick *every minute / hour / day / week / month / year*, then refine — e.g. "every 15 minutes, weekdays only, 09:00–17:00" generates `*/15 9-17 * * 1-5`. The next-run preview uses the host browser's local time zone, which usually matches what `/etc/crontab` would do on a server in the same zone. If your production cron runs in UTC, mentally subtract or add the offset before deploying. The parser is self-contained — no network requests, no third-party cron library — so the expression and your machine's wall clock never leave the page.

Examples

Top-of-the-hour maintenance hook

Input
expression:  0 * * * *
starting:    Mon 2026-05-18 14:00 local time
preview:     next 5 runs
Output
Mon 2026-05-18 15:00
Mon 2026-05-18 16:00
Mon 2026-05-18 17:00
Mon 2026-05-18 18:00
Mon 2026-05-18 19:00

`0 * * * *` is the canonical "every hour on the hour" expression — minute is fixed at 0, every other field is `*`. A common mistake is writing `* * * * *` for "every hour", which actually fires every minute (60× more often). When you really want hourly, anchor the minute field. For sub-hour cadences use `*/15` or `0,15,30,45`; the two are equivalent for whole-hour-aligned schedules but `0,15,30,45` survives correctly across HUP/restart timing on Vixie cron, while `*/15` is read once at crontab parse time.

Weekday business-hours polling

Input
expression:  */15 9-17 * * 1-5
interpret:   every 15 minutes, 09:00–17:45, Monday–Friday
preview:     next 4 runs after Fri 2026-05-15 17:45
Output
Mon 2026-05-18 09:00
Mon 2026-05-18 09:15
Mon 2026-05-18 09:30
Mon 2026-05-18 09:45

# Note: 17:00–17:45 inclusive on Friday, then the cron sleeps the entire
# weekend and resumes Monday 09:00. The 4× /hour cadence × 9 active hours
# × 5 weekdays = 180 fires per week.

A typical business-hours health check. The hour range `9-17` is *inclusive on both ends*, so 17:45 still fires. Day-of-week `1-5` means Monday through Friday (Sunday=0, Saturday=6). The combination of `*/15 9-17` produces 4 fires per hour × 9 hours = 36 fires per weekday, 180 per week. If you also need a final fire at 18:00 sharp before the system idles for the night, add it as an OR: switch to `0,15,30,45 9-17 * * 1-5` and add a separate line for `0 18 * * 1-5`. A single expression cannot OR distinct hour patterns.

Month-end report with DOM/DOW gotcha

Input
expression:  0 9 28-31 * 1
interpret:   POSIX (AND) — only when the 28th-31st is also a Monday
             Vixie (OR) — every day 28-31 OR every Monday
Output
POSIX behavior (this parser):
  Mon 2026-12-28 09:00     # 28th is a Monday
  Mon 2027-03-29 09:00     # next 28-31 + Monday match
  ...

Vixie crond behavior (most Linux distros):
  Every day 28-31 of any month
  PLUS every Monday
  ≈ 4 + 4 = 8 fires per month on average

This is the most-cited cron pitfall. POSIX § says day-of-month and day-of-week are ANDed when both are restricted (intersection); Vixie cron — which ships on most Linux distros via `cronie` or `cron` — ORs them (union). The same expression can fire 5× more often on a real production box than your local test. Use the parser to spot-check expressions where you restrict both fields. If you actually want monthly month-end runs, write `0 9 28-31 * *` with day-of-week left wide open, and add a date check inside the job itself: `[ "$(date -d tomorrow +%d)" = "01" ] && run.sh` only fires on the *true* last day.

FAQ

Why are there 5 fields here when I see 6 or 7 fields elsewhere?

Standard Unix cron — what `/etc/crontab` parses on Linux, BSD, and macOS — uses 5 fields: minute, hour, day-of-month, month, day-of-week. The 6-field variant adds a leading **seconds** field and is what Quartz Scheduler (Java) and many AWS / Spring schedulers accept. The 7-field variant adds a trailing **year** field and is Quartz-specific. The leading-seconds form is also used by some Go libraries like robfig/cron when configured with the `WithSeconds` option. If you paste a 6-field expression here, the parser will report "too many fields" — drop the seconds (or the year) and parse just the minute-onward middle five. Standard `crond` cannot fire faster than once per minute regardless of dialect.

Does cron support `@daily`, `@reboot`, and other shortcuts?

Vixie cron supports seven nicknames: `@yearly` / `@annually` (= `0 0 1 1 *`), `@monthly` (= `0 0 1 * *`), `@weekly` (= `0 0 * * 0`), `@daily` / `@midnight` (= `0 0 * * *`), `@hourly` (= `0 * * * *`), and `@reboot` (run once at system boot). The nicknames are a Vixie extension, not in POSIX, so they may not work on minimal busybox-cron used in some Docker base images. This parser does **not** accept nicknames in the parse box — paste the expanded 5-field form. `@reboot` has no equivalent expression because it is event-triggered (boot), not time-triggered; for similar "run once at startup" semantics, systemd timers with `OnBootSec` are the modern replacement.

What time zone does this parser use? My production cron runs in UTC.

The next-run preview uses your browser's local time zone (read via `Intl.DateTimeFormat().resolvedOptions().timeZone`). If your server runs in UTC and your browser is in JST or KST, the preview will be 9 hours later than what actually happens on the server. Most modern crons honor a `CRON_TZ=...` variable or `TZ=...` in the environment, set per-line or globally in `/etc/crontab`. On systemd timers, the `OnCalendar=` directive accepts an explicit zone like `OnCalendar=Mon..Fri 09:00 Asia/Tokyo`. Daylight saving makes the question worse: a job scheduled for 02:30 local time in a DST-observing zone will skip on the spring-forward day and run twice on the fall-back day under naive cron, which is why running production jobs in UTC is the standard recommendation.

How is `*/5` different from `0/5` or `0,5,10,15,...`?

In the minute field, all three forms are equivalent on POSIX/Vixie cron: they fire at minute 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55. The differences appear with non-zero starting points and non-standard ranges. `5/15` (Quartz) means "starting at 5, every 15" → 5, 20, 35, 50, which `*/15` does *not* produce. `1-30/5` means "every 5 minutes within 1–30" → 1, 6, 11, 16, 21, 26. POSIX/Vixie cron supports the `a-b/n` form but not the `a/n` (start-and-step) form — that's a Quartz extension. When in doubt, expand to an explicit list `0,5,10,...` for maximum portability. The list form is also what you want if you need to skip minutes — `0,3,6,9` fires 4 times per hour at irregular gaps.

Why does my Linux cron fire on different days than the parser predicts?

Almost certainly the DOM/DOW disagreement covered in example 3. This parser follows POSIX § (AND); the cron daemon shipping with most Linux distributions follows the Vixie convention (OR). To verify on your server, run `man 5 crontab` and search for "day-of-week" — the manpage will spell out the local behavior. Other possible causes: time zone mismatch between your editing environment and the server (see the UTC FAQ above), `cron` not running (`systemctl status cron`), the user's `MAILTO=` failing silently because mail isn't configured, or the user's `PATH` missing `/usr/local/bin` so the script's shebang resolves but its first command fails. Add `* * * * * date >> /tmp/cron.log` to a test crontab to confirm cron is firing at all before debugging expression semantics.

When should I use cron versus systemd timers or Kubernetes CronJob?

Use **cron** for shell-script-style automation on a single Linux host where you control `/etc/crontab` and uptime: log rotation, backups, simple polling, hobby projects. Use **systemd timers** when you already manage the service with systemd and want unified logging in `journalctl`, dependency ordering (run *after* `network-online.target`), randomized delays to avoid thundering-herd, or persistence across boots for missed runs (`Persistent=true`). Use **Kubernetes CronJob** when the workload runs in a cluster — you get pod-level resource isolation, automatic restart/retry, and observability through your normal k8s tooling, at the cost of significant cold-start latency (10–30 s for a tiny job). For cloud-native one-offs without a cluster, **AWS EventBridge Scheduler**, **Cloud Scheduler**, and **Azure Logic Apps** all accept cron-style expressions and free you from running any compute when the job is idle.

Related concepts

Cron originated at AT&T Bell Labs as part of 7th-Edition Unix (1979), reportedly written by Brian Kernighan. The original was a daemon that scanned `/usr/lib/crontab` once a minute and exec'd any matching entries. **Paul Vixie's rewrite** in 1987 introduced the per-user crontab, the named day/month aliases, the `@daily` / `@reboot` nicknames, and crucially the **OR semantics for DOM + DOW** that diverged from the POSIX standard published the following year. Vixie cron became the de facto standard on Linux and BSD; modern derivatives include `cronie` (Red Hat / Fedora), `bcron` (Debian), and `dcron` (Alpine, Slackware). POSIX-strict implementations exist mainly in commercial Unix and embedded busybox-cron. The DOM/DOW divergence is the single most cited cron pitfall and predates POSIX itself by ~ 7 years.

The **format itself has dialects** beyond field count. `JAN-DEC` and `SUN-SAT` aliases are Vixie extensions and not POSIX. Quartz Scheduler adds the seconds field, the year field, the special characters `L` (last), `W` (weekday), `#` (nth weekday of month), and `?` (no specific value, for DOM/DOW). AWS EventBridge follows Quartz semantics but adds `?` requirements. Spring `@Scheduled` follows yet another variant. When you copy a cron expression between systems, verify field count, the DOM/DOW semantic, and whether named aliases are supported — a working expression in one system can silently misbehave or refuse to parse in another.

Three **modern alternatives** have eaten cron's lunch in different domains. **systemd timers** replace cron on systemd-managed Linux servers (most distributions since 2015) — they integrate with the unit system, log to journald, support randomization and persistence, and accept the more readable `OnCalendar=Mon..Fri 09:00` syntax. **Kubernetes CronJob** brings cron semantics to containerized workloads with pod isolation and retry policies. **Cloud-managed schedulers** (AWS EventBridge Scheduler, Google Cloud Scheduler, Azure Logic Apps Scheduler) accept cron expressions but run on managed infrastructure, eliminating the always-on host. For one-off task scheduling within an application, libraries like Java's Quartz, Python's APScheduler, Go's robfig/cron, and Node's node-cron embed cron semantics directly. Despite these alternatives, plain crontab remains alive on every Unix-like machine — its small surface area and `/etc/crontab` simplicity mean it will outlive most of its replacements.

Related articles

Related tools