Cron-Ping
Log inStart for free

Cron expression validator: what does this line actually do?

Last updated: July 2026 · By Yohann Kipfer, Cron-Ping

In short

A cron expression is five fields separated by spaces — minute hour day-of-month month day-of-week. Values, ranges (1-5), lists (1,15) and steps (*/10) are allowed in each. The line below runs at minute 0 and 30 of every hour, on weekdays:

0,30 * * * 1-5

Paste yours in the box, it stays in your browser. The decoder expands each field to the values it really matches, shows the next five runs in your timezone, and flags the traps below — the ones a validator says nothing about because, syntactically, the line is fine.

Decode a cron expression

Runs locally in your browser. Nothing is sent anywhere, no account.

Field-by-field breakdown of the expression
FieldYou wroteMatches
minute*/50, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55· 12 values
hour*every value
day of month*every value
month*every value
day of week*every value
Nothing suspicious. Fields are unambiguous, steps divide evenly, and this line means the same thing on every cron implementation.
Next 5 runs

The five fields

Order matters and there is no seconds field. If you're staring at six columns, you're reading Quartz or node-cron, not crontab.

The five crontab fields, their range and what they accept
FieldRangeNotes
minute0-59The only field with no shorthand. */1 and * are the same thing.
hour0-2324-hour clock, in the server's timezone — not yours. 0 is midnight.
day of month1-31A 31 silently skips February, April, June, September and November.
month1-12jan-dec accepted as plain values (* * * jul *), not in ranges.
day of week0-70 and 7 are both Sunday. mon-sun accepted as plain values.

Cron syntax cheatsheet

Five operators, that's the whole language. Every expression you'll ever read is a combination of these.

Cron operators, their meaning and an example
SyntaxMeansExample
*every value of the field* * * * * — every minute
5exactly this value5 * * * * — at minute 5 of every hour
1,15,30a list of values0 1,13 * * * — at 01:00 and 13:00
1-5a range, both ends included0 9 * * 1-5 — 09:00 Monday to Friday
*/10every n-th value, from the start of the range*/10 * * * * — at :00, :10, :20 …
0-30/10a step inside a range0-30/10 * * * * — at :00, :10, :20, :30 only
jul, fria name instead of a number0 0 * jul * — every day in July

On top of that, crontab(5) defines seven shorthands: @yearly (= @annually), @monthly, @weekly, @daily (= @midnight), @hourly and @reboot. Paste any of them above and the decoder expands it. They're readable, but they all fire on the hour — if a hundred @daily jobs share a host, they all start at 00:00 together. Spreading them out with real expressions costs one line and saves a thundering herd.

The three traps that pass validation

A line can be perfectly valid and still not do what you meant. These are the ones the decoder flags, because none of them raise an error anywhere.

Day-of-month and day-of-week are ORed, not ANDed

This is the oldest surprise in cron and it's in the man page: when both day fields are restricted, the command runs when either matches. The classic:

0 0 13 * fri    # NOT Friday the 13th

That line runs every 13th of the month and every Friday — roughly 64 times a year instead of one or two. When only one of the two day fields is restricted, the OR doesn't apply and everything behaves the way you'd expect. Friday the 13th genuinely can't be expressed in five fields; you test the date inside the script.

Steps don't wrap around

*/7 * * * * looks like « every 7 minutes ». It isn't. The step restarts at the top of each hour: :00, :07, :14 … :56, then :00 again — a 4-minute gap where every other one is 7. Same story with */45 (00:00 and 00:45, then a 15-minute wait), and with hours: 0 */5 * * * skips from 20:00 to midnight.

It only matters when the step doesn't divide the range: */5, */10, */15, */20 and */30 are exact on minutes. Anything else drifts, and the decoder tells you where.

Names work — until you put them in a range

0 0 * * mon is fine. 0 0 * * mon-fri is not portable: crontab(5) says names are accepted as values but « ranges or lists of names are not allowed ». Vixie cron on Debian happens to accept mon-fri anyway, which is exactly why it bites — the line works on your laptop and gets rejected by the busybox cron in a container.

Same class of problem as 5/10, which no version of crontab(5) documents: some parsers read it as « from 5 to the end of the range, every 10 », others refuse the line. Write 5-59/10. It's uglier and it survives the move to another host.

FAQ

How do I read a cron expression?

Left to right: minute, hour, day of month, month, day of week. A * means « every value of this field », a number means « only this one ». 30 3 * * * is minute 30, hour 3, any day, any month, any weekday — so 03:30 every night. The fields are ANDed, except the two day fields, which are ORed when both are restricted.

What is */5 * * * *?

Every 5 minutes: minute 0, 5, 10 … 55, of every hour, every day. It's the shortest interval most people actually need, and 5 divides 60 exactly, so there's no wrap-around gap. */7 doesn't have that property.

Which timezone does cron use?

The server's local timezone, not yours and not UTC — unless the crontab sets CRON_TZ= at the top (Vixie cron) or you're on a distro that ignores it. The preview above uses the timezone of the browser you're reading this in, which is a good sanity check but not proof: run date on the server to be sure.

Is there a seconds field?

Not in Linux crontab. Five fields, one-minute resolution, full stop. Six-field expressions come from Quartz, Spring, node-cron or croniter — all of them prepend seconds. If you paste one above, the decoder says so rather than guessing.

Why did my cron job not run even though the expression is right?

Because a valid expression is only step one. The usual suspects: the crontab has no trailing newline, PATH is nearly empty under cron so python isn't found, the script needs a TTY, or the previous run is still holding a lock. Cron won't tell you — it has no « missed run » event. That's what a heartbeat is for.

The expression is right. Did the job run?

A validator tells you when a line is supposed to fire. It can't tell you the server was rebooted, the disk filled up or the script died at 03:31. Add && curl https://cron-ping.com/p/<token> at the end of the line: if the ping doesn't arrive inside the grace period, you get an email or a Slack message.

Create a free check

Free plan: 10 checks, email alerts, 7-day history. No card. EU-hosted.

Related

Cron job every 5 minutes: building the lineThe other half: curl flags, %-escaping, flock, and a builder that outputs the whole crontab line.Monitoring a Linux cron job that stopped runningChecking crond, reading the mail, spotting a crontab that got clobbered.Cron-Ping documentationSuccess, start and fail pings, log capture, curl/wget/Python snippets.A European Healthchecks.io alternativeWhat we do differently, and when Healthchecks.io self-hosted is the better call.

Sources