cronsaidwhat.com Runs in your browser
Schedule → expression

Generate a cron expression

Set each field to every, specific values, a range, or a step — months and weekdays by name — and copy the expression with a plain-English readback and the next five runs.

Cron expression builder

Start from

Every minute — writes * in this field.

0–59, comma-separated

to
every minutes

Every hour — writes * in this field.

0–23, on a 24-hour clock

to
every hours

Every day — writes * in this field.

1–31, comma-separated

to
every days

Every month — writes * in this field.

to
every months

Every day of the week — writes * in this field.

to
every days

0 9 * * 1-5
In English
At 09:00, on Monday, Tuesday, Wednesday, Thursday, and Friday.

Next runs

  1. Fire times appear here once the page's script loads.

From schedule to syntax

Every cron expression is five fields — minute, hour, day of month, month, day of week — and every field is built from the same four shapes. That is the whole grammar, and it is exactly what the mode selector above exposes:

  • Every* — the field places no restriction at all.
  • Specific values → a comma list like 0,15,30 — the field matches any one of them.
  • Range9-17 — inclusive at both ends.
  • Step*/5 — every 5th value, counting from the start of the field's range.

The builder normalises as it writes. Lists come out sorted and deduplicated; a range or list that covers the whole field collapses to *; a step of 1 is * too; and Sunday is always written as 0, never 7, though cron accepts both. The output is the shortest expression that means what you selected — which matters, because crontabs get read far more often than they get written. To go the other direction and decode an expression someone else wrote, paste it into the cron explainer on the homepage.

The one trap the builder cannot remove

Day of month and day of week are the only two fields with an unusual relationship: when both are restricted, cron fires when either matches. Set day of month to 1 and day of week to Monday and the job runs on the first of every month and on every Monday — not only on Mondays that fall on the 1st. There is no way to express the "and" in standard cron syntax, so no generator can produce it. The reliable workaround is to schedule the looser condition and enforce the stricter one inside the job, for example [ "$(date +\%u)" = 1 ] || exit 0 at the top of a script scheduled for the 1st. (The % is escaped because a bare percent sign ends the command line in a crontab — a second trap that bites anyone embedding date formats.)

Recipes

The schedules that cover most real crontabs. Any of them can be reproduced with the controls above, then adjusted:

ExpressionRuns
*/5 * * * *Every 5 minutes
0 * * * *Hourly, on the hour
0 0 * * *Daily at midnight
0 9 * * 1-5Weekdays at 09:00
*/15 9-17 * * 1-5Every 15 minutes, 09:00–17:45, weekdays
0 2 * * 0Sundays at 02:00
30 4 1,15 * *04:30 on the 1st and 15th
0 0 1 * *First of every month, midnight
0 6 * * 1Mondays at 06:00
0 0 1 1 *Once a year, 1 January

One recipe deserves a warning: "every N days" via a step in the day-of-month field, like 0 0 */2 * *. The step resets at each month boundary, so a 31-day month ends on day 31 and the next month starts on day 1 — two consecutive runs. Cron has no notion of "every second day" across months; for that, run daily and let the job decide, or use a systemd timer.

Timezones, and when the previews fire

The next-run preview is computed in UTC and shown alongside your browser's local time, so the offset is visible rather than surprising. A real cron daemon evaluates the expression against the server's local clock — which on a cloud VM is usually UTC, and on anything older is usually whatever the machine inherited. Modern crons let a crontab pin its own zone with a CRON_TZ=Europe/London line at the top. Wherever the zone comes from, daylight saving is the hazard: a job scheduled at 02:30 in a DST-observing zone is skipped on spring-forward night and runs twice on fall-back night. Scheduling in UTC avoids the problem entirely.

Seconds, years, and other dialects

Standard cron — the thing crontab -e edits — is exactly five fields, and that is what this builder generates. Quartz, and Spring's @Scheduled on top of it, prepend a seconds field and allow an optional year at the end, so a Quartz expression has six or seven fields and is a syntax error in a crontab. The conversion is mechanical: prepend 0 to go to Quartz, drop the first field to come back. Vixie cron also offers shorthands — @hourly, @daily, @weekly, @monthly, @reboot — which are aliases for five-field expressions (except @reboot, which has no equivalent). The explainer expands all of them.

Common questions

How do I run a job every N minutes?

Set the minute field to step mode: every 5 minutes is '*/5 * * * *'. One caveat — the step counts from the start of the field's range and resets each hour, so '*/7' fires at minutes 0, 7, 14, 21, 28, 35, 42, 49, and 56, then jumps back to 0. The gap between :56 and :00 is four minutes, not seven. Steps that divide 60 evenly (5, 10, 15, 20, 30) are the only ones with a truly constant interval.

Why does restricting both day-of-month and day-of-week fire more often than expected?

Because cron treats those two fields as an or, not an and. '0 0 13 * FRI' runs on the 13th of every month and on every Friday — roughly 64 times a year, not just Friday the 13th. The builder lets you produce such an expression because it is valid cron, but if you meant "both must match", schedule the looser field and test the other inside the job itself.

Can a cron expression include seconds?

Not in standard cron — crontab takes exactly five fields and rejects six. Quartz and Spring's @Scheduled use a six- or seven-field dialect with seconds first (and an optional year last), so '0 0 9 * * MON-FRI' is a valid Quartz schedule but a syntax error in a crontab. To convert a Quartz expression, drop the leading seconds field; going the other way, prepend a '0'.

What timezone are the previewed run times in?

The next runs are computed in UTC and shown in both UTC and your browser's timezone. A real cron daemon uses the server's local time unless you set CRON_TZ or TZ at the top of the crontab. Jobs scheduled in a zone that observes daylight saving can fire twice or not at all on changeover nights — scheduling in UTC, or between 03:00 and 01:59 local, sidesteps both failure modes.

How do I write "every weekday at 9am"?

Minute 0, hour 9, day of week Monday through Friday: '0 9 * * 1-5'. Most crons also accept names in that field, so '0 9 * * MON-FRI' is equivalent and easier to read in a code review. Leave day of month and month as '*' — restricting them would silently change the schedule via the or-rule between the two day fields.

Why did the builder write * instead of my range or step?

Because the expression means the same thing either way, and the shorter form reads better. A range covering the whole field (hours 0–23), a list containing every value, or a step of 1 all match exactly what '*' matches, so the builder normalises them. It also sorts and deduplicates lists, and writes Sunday as 0 rather than 7 — both are legal, but 0 is what every cron understands.