StringToolsStringTools

Cron Expression Generator and Explainer

Build a schedule with pickers, or paste an expression you inherited and read it back in plain English with the next five times it would fire. It also handles the one rule that quietly breaks more cron jobs than any other: when both day fields are set, cron runs the job on either match, not both.

Common schedules

Minute*/15Hour*Day of month*Month*Day of week*

Every 15 minutes every day.

Next 5 runs

Calculating…

Times are shown in your browser’s timezone. Your server almost certainly runs cron in its own timezone — often UTC — so a job that looks like 9am here may fire at a different local hour on the machine. Check with date on the server before you trust an hour.

Field by field
FieldValueAllowedMatches
Minute*/150-590, 15, 30 and 45
Hour*0-23 (24-hour clock)every value (0–23)
Day of month*1-31every value (1–31)
Month*1-12 or JAN-DECJanuary, February, March, April and 8 more
Day of week*0-7 or SUN-SAT (0 and 7 are both Sunday)Sunday, Monday, Tuesday, Wednesday and 3 more

Build it with pickers

These controls write the expression above, and they update whenever you edit it by hand. A field you typed by hand that does not fit a picker (like 1-5,10) shows as Custom and is left untouched.

Minute

Hour

Day of month

Month

Day of week

Syntax this tool understands
*Every value in the field* * * * * — every minute
5One exact value5 * * * * — at 5 minutes past every hour
1,15A list of values0 0 1,15 * * — the 1st and the 15th
1-5An inclusive range0 9 * * 1-5 — 9am Monday to Friday
*/nEvery n-th value from the start of the field*/15 * * * * — :00 :15 :30 :45
a-b/nEvery n-th value inside a range0 8-18/2 * * * — 08:00, 10:00 … 18:00
a/nEvery n-th value from a to the end of the field0 9/3 * * * — 09:00, 12:00, 15:00, 18:00, 21:00
JAN-DECMonth names (case-insensitive)0 0 1 JAN,JUL * — 1 Jan and 1 Jul
SUN-SATDay names (case-insensitive)0 17 * * FRI — 5pm every Friday
0 and 7Both mean Sunday in day-of-week0 0 * * 0 is identical to 0 0 * * 7

This parses the standard 5-field Unix cron format (minute, hour, day-of-month, month, day-of-week) used by Vixie/cronie crontabs on Linux and macOS. It does not parse 6-field expressions that add a leading seconds column (common in Node schedulers, Kubernetes tooling and Spring), nor Quartz expressions, which add seconds and a year and use ?, L, W and # — those will be flagged rather than silently mis-read. Shorthands such as @daily and @reboot are crontab conveniences, not field expressions. Next-run times are computed in your browser from your device clock; nothing you type is sent anywhere.

TL;DR

Five fields, in order: minute, hour, day-of-month, month, day-of-week. The one that catches everyone: if you restrict both day fields, cron runs the job when either matches — so 0 12 13 * 5 is every 13th plus every Friday, not Friday the 13th. Also worth knowing: */n counts from the start of the field rather than from now, 0 and 7 both mean Sunday, and the schedule runs in the server’s timezone, which is usually UTC.

The OR rule: why 0 12 13 * 5 is not Friday the 13th

Four of the five cron fields are combined with AND. Minute, hour and month all have to match at once, exactly as you would expect. The two day fields are the exception, and the rule is inverted: when day-of-month and day-of-week are both restricted — meaning neither is * — the job runs whenever either one matches. This is not a quirk of one implementation. It is written into the POSIX specification for crontab and repeated in the crontab(5) manual page on Linux and macOS.

So the expression that looks like it means “noon on Friday the 13th” means “noon on every 13th, and also noon on every Friday”. Here is September 2026, which has four Fridays and a 13th that falls on a Sunday:

ExpressionFires in September 2026Runs
0 12 13 * *Sun 13 only1
0 12 * * 5Fri 4, 11, 18, 254
0 12 13 * 5Fri 4, 11, Sun 13, Fri 18, 255

Note the Sunday in the middle. That is the 13th matching on its own, with the weekday field ignored for that day.

Stretched over a year the gap is brutal. In 2027 there are 53 Fridays and 12 thirteenths, overlapping exactly once — Friday 13 August 2027. A real Friday-the-13th job should run once. 0 12 13 * 5 runs 64 times. If that job sends an email, bills a customer, or posts to a channel, you find out the expensive way.

The tool above implements this rule rather than pretending it does not exist: when both day fields are restricted, the next-run list uses OR, the plain-English sentence spells out “cron matches either day rule, not both”, and an amber warning appears under the field breakdown. Try pasting 0 12 13 * 5 and watch a Sunday appear in a list of Fridays.

How to actually schedule Friday the 13th

You cannot, in the five fields alone — the OR rule makes the intersection unexpressible. The standard workaround is to schedule on one day field and enforce the other inside the script: run it with 0 12 13 * * and have the command exit immediately unless the weekday is Friday, for example a first line of [ "$(date +\%u)" = "5" ] || exit 0. The backslash matters: inside a crontab a bare % is treated as a newline, so every percent sign in a command has to be escaped. The same guard pattern covers “first Monday of the month” and every other rule cron cannot express on its own.

And the simple prevention: restrict at most one day field. If day-of-week carries the meaning, leave day-of-month as *, and the other way round. The moment both are anything other than *, you are in OR territory whether you meant to be or not.

The five fields, position by position

Fields are separated by whitespace and their meaning comes entirely from their position. Miscount by one and the expression is still valid — it just runs at the wrong time, which is why a five-field expression that “works” is not the same as one that is right.

#FieldRangeAllowedExample
1Minute0–59* , - /30 * * * * — 30 minutes past every hour
2Hour0–23, 24-hour clock* , - /0 22 * * * — 22:00 every day
3Day of month1–31* , - /0 0 1 * * — midnight on the 1st
4Month1–12 or JAN–DEC* , - / and names0 0 1 JAN,JUL * — 1 Jan and 1 Jul
5Day of week0–7 or SUN–SAT (0 and 7 are both Sunday)* , - / and names0 17 * * FRI — 17:00 every Friday

Two details in that last row do real damage. First, 0 and 7 both mean Sunday, so the range is 0–7 rather than 0–6 and 0 0 * * 0 and 0 0 * * 7 are identical schedules. Paste both into the tool and the next-run lists match exactly. Second, day names are accepted but ranges of names behave like ranges of numbers, so FRI-SUN reads as 5 to 0, which runs backwards and is rejected — write FRI,SAT,SUN or 5,6,0 instead.

A dozen schedules worth copying

Every expression here was checked against the tool’s own next-run engine, and the English column describes what actually fires rather than what the expression looks like it should do.

ExpressionWhat it actually does
*/15 * * * *At :00, :15, :30 and :45 of every hour, every day.
0 * * * *At the top of every hour — 24 runs a day.
5 0 * * *Once a day at 00:05. Five past midnight, not midnight.
30 2 * * *Once a day at 02:30 — the hour daylight saving eats. See below.
0 9 * * 1-509:00 Monday through Friday. Nothing at the weekend.
0 8-18/2 * * 1-508:00, 10:00, 12:00, 14:00, 16:00 and 18:00 on weekdays only.
0 2 * * 002:00 every Sunday. 0 2 * * 7 is the identical schedule.
0 0 1 * *Midnight on the 1st of every month — 12 runs a year.
0 0 1,15 * *Midnight on the 1st and on the 15th — twice a month.
0 3 1 */3 *03:00 on 1 January, 1 April, 1 July and 1 October.
0 0 1 1 *Midnight on 1 January. Once a year.
0 12 13 * 5The trap: noon on every 13th AND on every Friday — not Friday the 13th.

Shorthands like @daily, @weekly and @reboot are crontab conveniences rather than field expressions, so they are not something this tool parses. @daily is exactly 0 0 * * *; @reboot has no clock equivalent at all.

What */n really means, and where the gap goes

The near-universal misreading is that */15 means “every 15 minutes from whenever the job last ran”. It does not. A step selects every n-th value counting from the start of the field, and the count restarts at each field boundary. For minutes, which run 0 to 59, */15 expands to 0, 15, 30 and 45 — exactly what you wanted, but only because 15 divides 60.

Pick a step that does not divide evenly and the interval is no longer the interval. */25 * * * * fires at :00, :25 and :50 — then the hour resets, so the fourth gap is 10 minutes, not 25. */40 * * * * is worse: :00, :40, then 20 minutes later it is :00 again. 0 */5 * * * gives you 00:00, 05:00, 10:00, 15:00, 20:00 and then a four-hour hole across midnight. And 0 0 */2 * * means the 1st, 3rd, 5th and so on — so a 31-day month is followed by the 1st of the next one, and the job runs on two consecutive days at the turn of the month.

A step can also be anchored. 0 8-18/2 * * * counts in twos from 8, giving 08:00 through 18:00, and 0 9/3 * * * counts in threes from 9 to the end of the field: 09:00, 12:00, 15:00, 18:00 and 21:00. If the interval genuinely matters — a lock renewal, a heartbeat — anchor the range rather than trusting */n to be even.

A limitation worth knowing: the tool’s one-line summary describes */25 * * * * as “every 25 minutes”, because that is how the syntax reads. The next-run list underneath is the honest answer — :00, :25, :50, :00. When a step does not divide its field evenly, trust the run list, not the sentence.

The clock cron uses is not your clock

A cron expression carries no timezone. It is evaluated in the timezone of the machine running the daemon, which on cloud servers and inside most container images is UTC. A schedule you wrote as “9am” in London fires at 9am UTC, which is 10am local for half the year — and for a team in New York the same line is a 4am or 5am job. Run date on the actual host before you trust an hour. The next-run list in the tool above is computed in your browser’s timezone and prints which one that is, precisely so you can see the difference rather than assume it away.

If the server is not on UTC, daylight saving becomes a scheduling problem. Take 30 2 * * *, a daily 02:30 job — a very common slot, because it is quiet. In a US or European timezone, the spring-forward night has no 02:30 at all: the clock jumps from 01:59:59 to 03:00:00. On the autumn night, 02:30 happens twice.

What happens then depends on the implementation, and this is where portability dies. The Vixie-derived cron shipped by Debian and Ubuntu documents its behaviour in cron(8): for a clock change of less than three hours, fixed-time jobs skipped by a forward jump are run shortly after the change, and jobs falling inside a repeated hour are not run a second time — and this applies only to jobs at a particular time, not to ones with * in the hour or minute. Other schedulers make different choices, and some simply miss the run or fire it twice. If a job must not double-fire, do not rely on the daemon: make the job idempotent, or move the host to UTC and do the timezone arithmetic yourself.

The practical rule is boring and works: keep servers on UTC, keep expressions in UTC, and translate for humans in the documentation. If a job genuinely has to land at a local wall-clock time — a report that must arrive before the office opens — use a scheduler that takes an explicit timezone rather than encoding your guess about the offset into the hour field.

Six fields means you are in a different dialect

“Cron syntax” is a family, not a standard, and the differences are silent until something fires at the wrong time. This tool parses standard 5-field Unix cron — the Vixie and cronie crontabs on Linux and macOS. If your expression has six or seven fields, it belongs to a relative:

  • A leading seconds column. Spring’s @Scheduled and several Node schedulers put seconds first, so a six-field expression is usually just yours with a seconds field bolted on. Drop the first column to get the 5-field equivalent.
  • Quartz. Seconds first, an optional trailing year, and extra characters — ? for “no specific value”, L for last, W for nearest weekday, # for “n-th weekday of the month”. Crucially its day-of-week runs 1–7 with 1 meaning Sunday, so a bare 5 is Friday in Unix cron and Thursday in Quartz. Quartz also sidesteps the OR problem by refusing to let you specify both day fields — one of them must be ?.
  • AWS EventBridge. Six fields with a year, evaluated in UTC unless you set a timezone, and it borrows the Quartz rule that one day field must be ?.
  • GitHub Actions. Ordinary 5-field cron, always UTC, and scheduled runs can be delayed or dropped under load — treat the time as a hint, not a guarantee.
  • Kubernetes CronJob. Standard 5-field syntax, and it inherits the OR rule exactly as described above. It runs in the controller’s timezone unless the CronJob sets one explicitly.

Rather than guess, the tool refuses. A six-field expression is flagged as such, and ?, L, W and # are named as Quartz extensions instead of being quietly ignored — a wrong answer delivered confidently is worse than no answer. Everything happens in your browser: nothing you type is uploaded, and there is no account, no saving and no history.

Frequently asked questions

Why does my cron job with both a day-of-month and a day-of-week run too often?

Because standard cron treats the two day fields as OR, not AND. When day-of-month and day-of-week are both restricted, the job runs whenever either one matches. So 0 12 13 * 5 does not mean Friday the 13th — it fires at noon on every 13th and on every Friday. In 2027 that is 64 days instead of one. Restrict only one day field, and if you truly need both conditions, put the second one in the script as a guard.

How do I make a cron job run only on Friday the 13th?

You cannot express it in the five fields alone, because the OR rule turns the combination into a union. Schedule it on the day-of-month only, with 0 12 13 * *, then exit early inside the script unless the weekday is Friday — for example a first line of [ "$(date +\%u)" = "5" ] || exit 0. The percent sign must be escaped in a crontab, because crontab treats a bare % as a newline.

Does 0 mean Sunday in cron, or does 7?

Both do. The day-of-week field accepts 0 to 7, and 0 and 7 are both Sunday, so 0 0 * * 0 and 0 0 * * 7 are the same schedule. The tool normalises 7 to 0 internally, which is why the run lists are identical. Be careful when moving an expression to Quartz, where day-of-week is 1 to 7 with 1 meaning Sunday, so the same digit means a different day.

What does */15 actually mean in a cron expression?

It means every 15th value counting from the start of the field, not every 15 minutes from now. The minute field runs 0 to 59, so */15 fires at :00, :15, :30 and :45 of every hour. When the step does not divide the field evenly the gap resets at the field boundary: */25 fires at :00, :25 and :50, so the last gap of the hour is only 10 minutes.

Which timezone does a cron job use?

The server’s, not yours. A crontab is evaluated in the timezone of the machine running cron, which on cloud servers and in most containers is UTC. Check it with the date command on that host before you trust an hour. The next-run list in this tool uses your browser’s timezone, and it shows which one it used so you can compare the two.

Why does my expression have six fields and get rejected?

Six-field expressions belong to a different dialect. Quartz, Spring’s @Scheduled, and several Node schedulers put a seconds column first, and Quartz adds a trailing year plus the ? L W and # characters. This tool parses standard 5-field Unix cron only, so it flags a six-field expression rather than silently misreading it. If yours starts with a seconds column, drop it to get the equivalent 5-field schedule.