UUsefulCrate Your files never leave your browser

Home › Blog › Developer

Cron Expressions Explained: The Cheat Sheet for Schedules You Actually Need

Five tiny fields, one union quirk, a timezone trap, and a cheat sheet for the ten schedules everyone ends up needing — each example checkable in the browser before it touches a server.

2026-11-03 · 6 min read

If you have ever typed */5 * * * * into a crontab file and hoped for the best, you already know the problem with cron: the syntax is five tiny fields, and every one of them has a rule that is easy to get wrong. A schedule that fires at the wrong hour, or silently skips the last day of the month, or runs on a server in a timezone you did not pick, is more common than a schedule that is right.

A cron expression is five time fields — minute, hour, day-of-month, month, day-of-week — read left to right. Each field accepts a number, a list (8,20), a range (1-5), a step (*/15) or a wildcard (*). The server fires the job at every moment that matches all five fields.

This post is the cheat sheet for the ten schedules people actually need, followed by the handful of quirks that cause real incidents. You can test every example below in our Cron Explainer, which runs in your browser and shows the next run times without sending anything to a server.

The five fields, in one table

FieldRangeSpecial valuesMeaning
Minute0-59* , - /Minute of the hour
Hour0-23* , - /Hour of the day, server local time
Day of month1-31* , - / LCalendar day
Month1-12 (jan-dec)* , - /Month of the year
Day of week0-6 (sun-sat)* , - /0 and 7 are both Sunday

Read the expression as a filter: the job runs when all five fields match the current time. That “and” sounds obvious, but it is exactly where the most common surprise comes from — more on the day-fields quirk below.

The ten schedules you actually need

You wantExpressionNotes
Every 5 minutes*/5 * * * *Steps start from 0, so runs at :00, :05, :10…
Every hour, on the hour0 * * * *Not * * * * * — that is every minute
Daily at 9:000 9 * * *Server timezone, not yours (see below)
Weekdays at 9:000 9 * * 1-51 = Monday
Twice a day, 8:00 and 20:000 8,20 * * *Lists with a comma
Every Monday at 8:3030 8 * * 1Minute first, always
First day of the month, midnight0 0 1 * *Day of month is the third field
Quarterly (Jan/Apr/Jul/Oct 1st)0 0 1 1,4,7,10 *Months as a list
Every year on January 1st0 0 1 1 *The classic “happy new year” job
Every 10 minutes, weekdays only*/10 * * * 1-5Step plus range combine freely

One near-miss worth flagging: * * * * * is “every minute”, and it is the most expensive mistake to paste by accident. If you see a server mysteriously busy, a runaway every-minute job is a good first suspect.

The day-fields quirk that bites everyone once

Cron has two day fields: day-of-month and day-of-week. The rule in classic (Vixie) cron is:

If both day fields are restricted (neither is *), the job runs when either matches — not both.

So 0 9 13 * 5 does not mean “Friday the 13th”. It means “every Friday, plus the 13th of every month, whatever weekday it falls on”. Most people expect an intersection and get a union. If you actually want Friday the 13th, classic cron cannot express it in one line; you need a wrapper script that checks the date, or a scheduler with richer syntax.

Our Cron Explainer follows this classic semantics and shows you the actual next run times, so a union surprise shows up as visible dates before it becomes a production surprise.

Four more quirks that cause real incidents

Timezone. Cron runs in the server's local time. A job scheduled for 9:00 on a server set to UTC fires at 18:00 in Beijing and 4:00 in New York. Worse, some hosting platforms silently run containers in UTC. If the hour matters, check the server's timezone first — date on the same machine is the ground truth.

Steps are anchored at zero. */15 means 0, 15, 30, 45 — it does not align to when the server booted or when the file was saved. If you need runs at :10, :25, :40, :55, write 10-59/15.

Ranges do not wrap. A night window across midnight, like 22:00 to 02:00, cannot be written as 22-2 in classic cron. Write two lines: 0 22-23 * * * and 0 0-2 * * *.

February 29th exists. 0 0 29 2 * is a valid yearly schedule that only fires on leap years. It is a neat party trick and a genuinely correct way to run a once-every-four-years task.

How to check a schedule before trusting it

Read the expression out loud in field order — minute, hour, day-of-month, month, day-of-week — and it usually becomes obvious where you inverted something. Then verify mechanically:

  1. Paste the expression into the Cron Explainer and read the next five run times. Do the dates match your intent, including the timezone you assumed?
  2. For anything touching a specific weekday and a specific day-of-month, remember the union rule and check whether you actually wanted “either”.
  3. On the server itself, run the job's command once by hand. Cron only schedules; it does not make a broken command work.

If your job processes exports or logs with timestamps inside them, the Unix Timestamp Converter is handy for translating what the log says into what your clock says — the usual first step when you suspect a timezone mismatch.

Frequently asked questions

Is cron 5 fields or 6?

Classic crontab is 5. Some systems add fields: Quartz (Java) prefixes seconds for 7 total, and systemd timers replace the syntax entirely. When a parser complains, count your fields — a 6-field expression pasted into a 5-field crontab silently shifts every value one place left, which schedules the wrong thing with no error.

Why did my job not run at all?

The usual suspects, in order: the cron daemon is not running; the crontab you edited is not the crontab the server reads (check with crontab -l as the same user); the timezone is not what you assumed; or the expression is valid but matches nothing, like 0 9 31 2 * — February 31st never happens.

Does cron account for daylight saving time?

It runs on server local time, so a 2:30 schedule can fire twice or not at all on DST transition nights. If your server observes DST and the job matters, schedule it in UTC instead, where the clock never jumps.

What is the difference between */10 and 0,10,20,30,40,50?

Nothing, in classic cron — the step syntax is just shorthand for exactly that list. Anywhere they differ, the list is the one that does what the author meant.

← All posts