~/cron ☀ LIGHT 🔍 regex 🌐 subnet 🕑 clocks ☕ Support me apps ← about me
// crontab · kubernetes · quartz · spring · aws · azure · jenkins · github

Cron Expression Explainer — Plain English, Next Run Times and Timezone Checker

Paste a cron expression and read it back in plain English, with the next 12 run times in whatever timezone you actually care about. The catch nobody warns you about is that “cron” is not one language. 0 0 * * 1 means Monday on Linux and Sunday in Quartz. A five-field string is a syntax error to Spring. ? is mandatory on AWS and meaningless on a server. So this tool parses against the platform you pick — and then shows you what the same string would do on the other seven.

Which scheduler is this for?
// common cron expressions — click to load
// the same string on every other scheduler — click one to switch
Nothing leaves your browser. The parser, the schedule maths and the timezone conversions all run in this page. There is no request to any server — you can read the source, or disconnect and it still works.

The five fields, and the sixth that catches people out

A classic crontab line is five fields separated by spaces, and they are always in this order:

PositionFieldRangeNotes
1minute0–59
2hour0–2324-hour clock, no AM/PM
3day of month1–31see the OR trap below
4month1–12 or JAN–DEC
5day of week0–7 or SUN–SAT0 and 7 are both Sunday

Four characters do all the work. * means every value. , lists them (1,15). - is a range (9-17). / is a step, so */15 is every fifteenth. That is the entire language — everything else is a platform extension.

The sixth field is where it goes wrong. Quartz, Spring and Azure Functions put seconds first, so their expressions have six fields and everything shifts one place right. AWS EventBridge also uses six, but its extra field is a year on the end, not seconds at the front. Paste a five-field line into any of them and you get either a rejection or, worse, a schedule that runs sixty times more often than you meant. That is why this page asks which scheduler you are targeting before it tells you anything.

Day-of-month and day-of-week are an OR, not an AND

This is the single most expensive misunderstanding in cron, and it is genuinely counter-intuitive. In standard Vixie cron — Linux, Kubernetes, Jenkins, GitHub Actions — when both the day-of-month and day-of-week fields are restricted, the job runs when either one matches.

So 0 0 13 * 5 is not “midnight on Friday the 13th”. It is midnight on every 13th, and also midnight every Friday — about 60 runs a year instead of one or two. People discover this when a monthly report starts arriving weekly.

The rule only applies when both fields are restricted. If either is *, the other simply wins, which is why 0 0 * * 5 (every Friday) and 0 0 13 * * (every 13th) both behave exactly as you would expect.

There is no way to express a true AND in standard cron. The usual workaround is to schedule the broader of the two and test the date inside the job:

0 0 13 * * [ "$(date +\%u)" = "5" ] && /path/to/job

Quartz and AWS EventBridge dodge the problem entirely by making you write ? in whichever day field you are not using, so only one of them is ever active. This tool warns you the moment both fields are set on a dialect where the OR applies.

Timezones and daylight saving break more cron jobs than syntax errors do

Cron schedules wall-clock time, not elapsed time. It looks at the clock on the wall, and if the clock says the job is due, it runs. Twice a year that clock does something strange, and two predictable failures follow.

Spring forward: the run that never happens

When clocks jump from 02:00 to 03:00, the hour in between does not exist. A job scheduled for 30 2 * * * is simply skipped that day — no error, no log line, no alert. If that job was your nightly backup, you have a hole in your backups exactly once a year, and nothing tells you.

Fall back: the run that happens twice

When clocks go from 03:00 back to 02:00, the 02:00–03:00 hour is replayed. That same job runs twice, roughly an hour apart. If it is not idempotent — a billing run, an email send, a counter increment — you have just done it to every customer twice.

The run list on this page flags both cases explicitly, marking skipped runs and repeated hours against whichever timezone you select. The practical fix is to avoid scheduling anything between 01:00 and 03:00 local time, or to run in UTC, which has no daylight saving at all. Running in UTC means your “9am job” drifts an hour twice a year relative to office hours — usually the lesser evil, and always the more predictable one.

Where the timezone comes from also varies. A Linux crontab uses the server timezone, or a TZ= line at the top of the file. A Kubernetes CronJob is UTC unless you set spec.timeZone, which only exists from 1.27. AWS EventBridge cron() rules are UTC with no option at all. GitHub Actions is UTC, always.

The same string means different things on different platforms

Eight schedulers, eight sets of rules. These are the differences that actually change what runs when:

PlatformFieldsSunday isSeconds?L / W / #
Unix / crontab50 or 7nonono
Kubernetes CronJob50 or 7nonono
Quartz6 or 71yes, firstrequiredyes
Spring @Scheduled60 or 7yes, firstallowedno
AWS EventBridge61no (year last)requiredyes
Azure NCRONTAB60yes, firstnono
Jenkins50 or 7nonono (has H)
GitHub Actions50 or 7nonono

The day numbering is the dangerous one, because a Quartz expression copied from a Linux crontab is still valid — it just runs a day early, every time, for ever. In Quartz and AWS, Sunday is 1 and Saturday is 7. Everywhere else Sunday is 0. So 1 means Monday on your server and Sunday in your Java scheduler, and nothing will ever tell you.

The comparison panel above the examples shows, for whatever you have typed, which of the eight accept it and which of those give it a different meaning. If you are migrating a schedule between platforms, that panel is the whole reason this page exists.

Steps, ranges and the ones that quietly misfire

*/n reads as “every n”, and mostly behaves — but only when n divides the field range evenly. */15 in the minute field gives you 0, 15, 30, 45 and then wraps cleanly. */7 gives you 0, 7, 14, 21, 28, 35, 42, 49, 56 — and then the next run is 0, which is four minutes later, not seven. Every hour has one short gap. The same applies to */7 in hours, */45 in minutes, and anything else that does not divide its range.

A step can also start somewhere other than zero: 5/10 means “from 5, every 10” — 5, 15, 25, 35, 45, 55. And a range can carry a step, so 0-30/5 is every five minutes for the first half hour only.

Days that do not exist

0 0 31 * * looks monthly but runs seven times a year, because April, June, September and November have no 31st. 0 0 30 2 * parses perfectly and can never run. If you want the last day of the month on Linux there is no L — the idiom is to run on the 1st and subtract a day, or to check inside the job with [ "$(date -d tomorrow +\%d)" = "01" ]. On Quartz and AWS you can simply write L.

This page flags all of these as you type, along with the frequency: if an expression fires 1,440 times a day it will tell you, because a job that runs every minute and sometimes takes 70 seconds will happily start overlapping copies of itself until the box falls over. flock, a lock file, or concurrencyPolicy: Forbid on Kubernetes.

Related tools

## cron expressions — frequently asked questions

Reading and writing cron
How do I read a cron expression?

Read it left to right as minute, hour, day of month, month, day of week. 30 4 * * 1 is: minute 30, hour 4, any day of the month, any month, day-of-week 1 — so 04:30 every Monday.

The trick is that the fields you leave as * are the ones that set the frequency. A * in the day-of-month and month fields means “daily”; putting a number in the day-of-week field narrows it to weekly. Paste any expression into the box above and it will read it out for you.

What does * * * * * mean?

Every minute of every hour of every day — 1,440 runs a day, 10,080 a week. It is the most frequent schedule plain cron can express, since the smallest unit is one minute.

If a run can ever take longer than sixty seconds, cron starts the next one anyway and they overlap. Wrap the command in flock -n /tmp/job.lock, or set concurrencyPolicy: Forbid on a Kubernetes CronJob.

How do I write a cron job that runs every 5 minutes?

*/5 * * * *. The */5 in the minute field means “every fifth minute” — 0, 5, 10, 15 and so on. The four *s after it mean every hour, every day, every month, every weekday.

The same pattern gives you the rest: */10, */15, */30. Only use divisors of 60, or the wrap-around gap will be shorter than the interval — see the warning this tool shows for */7.

How do I run a job at a specific time every day?

Put the minute and hour in the first two fields and leave the rest as *. 30 3 * * * is 03:30 daily. The hour is a 24-hour clock, so 6pm is 18, not 6.

For several times a day, list them: 0 9,13,17 * * * runs at 09:00, 13:00 and 17:00. For a window, use a range with a step: 0 9-17 * * * is hourly through office hours.

What is the difference between */5 and 5 in a cron expression?

5 means exactly the value 5 — minute 5, once an hour. */5 means every fifth value — minutes 0, 5, 10, 15… twelve times an hour. One character, twelve times the load.

There is also 5/10, which is “start at 5, then every 10”, giving 5, 15, 25, 35, 45, 55.

The day-of-week trap
Why does my cron job run more often than expected?

Nine times out of ten it is the day-of-month / day-of-week rule. If both of those fields are set to something other than *, standard cron runs the job when either matches, not both.

0 0 13 * 5 looks like “Friday the 13th” and is actually “every 13th, plus every Friday”. This tool warns you the moment you write an expression with that shape.

The other common cause is a step that does not divide its range, like */7, or a five-field expression pasted into a six-field scheduler, where your minutes silently become seconds.

Is Sunday 0 or 1 in cron?

It depends on the scheduler, and this is the difference most likely to cost you a day.

  • Unix, Kubernetes, Jenkins, GitHub Actions, Spring: Sunday is 0. Monday is 1, Saturday is 6, and 7 is accepted as Sunday too.
  • Quartz and AWS EventBridge: Sunday is 1. Monday is 2, Saturday is 7. There is no 0.
  • Azure NCRONTAB: Sunday is 0, and 7 is an error.

So 0 0 * * 1 is Monday on a server and Sunday in Quartz. Using the three-letter names — MON, FRI — sidesteps the whole problem, and every dialect here accepts them.

How do I schedule the first Monday of the month?

On Quartz or AWS EventBridge there is direct syntax: 2#1 in the day-of-week field means “the first Monday” (remember Sunday is 1 there, so Monday is 2). The full Quartz expression is 0 0 9 ? * 2#1.

On Linux there is no #, so the idiom is to combine a day-of-month range with a weekday: 0 9 1-7 * 1 — but remember the OR rule makes that “days 1–7 or any Monday”, which is wrong. The correct version tests inside the job: 0 9 1-7 * * [ "$(date +\%u)" = "1" ] && /path/to/job.

Timezones and DST
What timezone does cron use?

Whatever the machine or service is configured for, and it differs by platform:

  • Linux crontab: the server's local timezone. You can override it per file with a TZ=Europe/Brussels line at the top.
  • Kubernetes CronJob: UTC, unless you set spec.timeZone — which only exists from Kubernetes 1.27.
  • AWS EventBridge cron(): always UTC, with no option. Use EventBridge Scheduler instead if you need a real timezone.
  • GitHub Actions: always UTC.
  • Quartz / Spring: the JVM default unless you set one explicitly.

Pick a zone in the selector above and the run list is recalculated in it, with the UTC instant shown alongside so you can check what the server will actually see.

What happens to cron jobs during daylight saving time?

Two things, both bad, once a year each.

When clocks go forward, an hour disappears. A job scheduled inside it — 30 2 * * * in most of Europe and North America — simply does not run that day. No error is raised. Nothing logs it.

When clocks go back, an hour repeats, and the job runs twice about an hour apart. For anything that sends, charges or increments, that is a real incident.

Set a timezone above and this tool marks both cases directly in the run list. The safest schedules avoid 01:00–03:00 local entirely, or run in UTC and accept the seasonal drift.

Should I run cron jobs in UTC?

Usually yes, for anything machine-facing. UTC has no daylight saving, so every day is exactly 24 hours and every schedule is exactly as frequent as it looks. Logs from different regions line up. Nothing runs twice, and nothing is skipped.

The cost is that a UTC job drifts an hour relative to local office hours twice a year. For a nightly batch nobody notices; for “email the team at 9am” it matters, and that is the case where a real timezone earns its complexity.

Platform differences
Why does my cron expression not work in AWS EventBridge?

Three likely reasons. Field count: EventBridge wants six fields, with a year at the end — 0 12 * * ? *, not 0 12 * * *. The question mark: exactly one of day-of-month and day-of-week must be ?; * * in both is rejected outright. Day numbering: Sunday is 1, not 0.

Also worth knowing: EventBridge cron() rules are always UTC, and the minimum resolution is one minute. Select AWS EventBridge above and this page will validate against exactly those rules.

What is the ? in a cron expression?

It means “no specific value” and it exists only to resolve the day-of-month versus day-of-week ambiguity. In Quartz and AWS EventBridge you must put ? in whichever of the two day fields you are not using, so only one is ever active — which is how those platforms avoid the OR trap that catches people on Linux.

Standard Unix cron does not understand ? at all. Paste one into a crontab and the line is rejected.

Does a Kubernetes CronJob use the same syntax as crontab?

Yes — five fields, Vixie syntax, and the @daily-style macros all work. The differences are operational rather than syntactic:

  • No @reboot — a cluster has no single boot moment.
  • UTC by default, with spec.timeZone only from 1.27.
  • startingDeadlineSeconds decides how late a missed run may still start. After 100 consecutive misses the controller gives up on the CronJob entirely and only an event records why.
  • concurrencyPolicy is how you stop overlapping runs — set it to Forbid rather than reaching for a lock file.
Why do my GitHub Actions scheduled workflows run late or not at all?

Because they are best-effort, and this is documented rather than a bug. Scheduled workflows go into a shared queue; delays of 5 to 30 minutes are normal, and at peak times — the top of the hour especially — runs are dropped completely.

Two more rules catch people: intervals shorter than five minutes are not honoured, and scheduled workflows are disabled automatically after 60 days without repository activity. If a job must happen on time, trigger it from something that guarantees delivery and use Actions only as the runner.

Special syntax
What does @daily, @hourly or @reboot mean?

They are shorthand macros:

MacroEquivalentMeaning
@yearly / @annually0 0 1 1 *midnight, 1 January
@monthly0 0 1 * *midnight on the 1st
@weekly0 0 * * 0midnight on Sunday
@daily / @midnight0 0 * * *every midnight
@hourly0 * * * *every hour, on the hour
@rebootonce, at startup

@reboot is the odd one out: it has no schedule at all, and it is not supported on Kubernetes, AWS, Azure or GitHub Actions. Nor are the other macros, on AWS and GitHub.

What do L, W and # mean in a cron expression?

Quartz extensions, also supported by AWS EventBridge, and unavailable everywhere else.

  • L in day-of-month is the last day of the month — 31 January, 28 or 29 February, 30 April. L-3 is three days before that.
  • L after a weekday is the last of that weekday: 6L is the last Friday in Quartz numbering.
  • W is the nearest weekday to a date, without leaving the month: 15W on a Sunday the 15th moves to Monday the 16th.
  • # picks the nth weekday: 6#3 is the third Friday.

None of these exist in standard cron. Select Quartz or AWS above to use them and see the real dates they resolve to.

Can cron run every second, or more often than once a minute?

Not in standard cron — one minute is the floor, because there is no seconds field. Schedulers that do have one are Quartz, Spring @Scheduled and Azure NCRONTAB, all of which put seconds first: */30 * * * * * is every thirty seconds in Spring.

On a plain server the usual workaround is a one-minute job that loops internally with sleep, or a systemd timer with OnUnitActiveSec, which handles sub-minute intervals properly and logs its runs.

Using this tool
Is my cron expression sent to a server?

No. The parser, the schedule calculation and the timezone conversions all run in the page, on your device. There is no API call, no logging and no analytics on what you type. You can open the network tab and watch nothing happen, or disconnect entirely — the page keeps working.

Can I share or bookmark a specific expression?

Yes. The link button copies a URL carrying the expression, the platform and the timezone, so whoever opens it sees exactly what you saw rather than their own defaults. The address bar updates as you type, so a plain bookmark works too.

How accurate are the next run times?

The schedule maths was tested against cron-parser, an independent implementation, across 650 computed run times covering steps, ranges, lists, leap years and month-end edge cases — every one identical. The timezone and daylight-saving handling is checked separately against hand-verified transition dates in Europe, North America and Australia, including a zone with a half-hour offset.

What the tool cannot know is whether your server is up, whether the previous run is still going, or whether the machine's clock is right. It tells you when the schedule fires, not whether the job succeeds.