Blog

Cron syntax explained, field by field

A practical guide to reading and writing cron expressions — the five fields, the operators, common recipes, and the timezone trap.

SteadyCron cronschedulingguide

Cron expressions look cryptic, but they follow a small, learnable grammar. Once you can read the five fields and four operators, 30 4 1,15 * * stops being a puzzle. This is a practical guide — and there’s a free explainer tool if you’d rather just paste an expression and see what it does.

The five fields

A standard cron expression is five space-separated fields:

┌───────────── minute        (0 - 59)
│ ┌─────────── hour          (0 - 23)
│ │ ┌───────── day of month  (1 - 31)
│ │ │ ┌─────── month         (1 - 12)
│ │ │ │ ┌───── day of week   (0 - 6, Sunday = 0)
│ │ │ │ │
* * * * *

Read left to right: minute, hour, day-of-month, month, day-of-week.

The four operators

  • * — every value. * * * * * means every minute.
  • , — a list. 1,15 in day-of-month means the 1st and the 15th.
  • - — a range. 9-17 in the hour field means 09:00 through 17:00.
  • / — a step. */15 in the minute field means every 15 minutes.

You combine them freely: 0 9-17 * * 1-5 is “on the hour, 9am–5pm, weekdays.”

Recipes you’ll actually use

ExpressionMeaning
*/15 * * * *Every 15 minutes
0 9 * * 1-5Weekdays at 09:00
30 4 1,15 * *04:30 on the 1st and 15th
0 */4 * * *Every 4 hours
0 2 * * *Daily at 02:00
0 0 * * 0Sundays at midnight

The day-of-month / day-of-week gotcha

If you set both day-of-month and day-of-week (to non-* values), most cron implementations treat it as an OR, not an AND. 0 0 13 * 5 doesn’t mean “Friday the 13th” — it means “the 13th of any month, and every Friday.” Keep one of the two as * unless you really mean the union.

The timezone trap

The biggest source of cron surprises isn’t syntax — it’s time. Server cron usually runs in UTC, but you think in local time, and twice a year daylight saving shifts the wall clock. A job set for “09:00” in a UTC crontab drifts an hour every spring and autumn relative to your office.

The fix is to schedule in a real timezone and let the scheduler handle DST. SteadyCron gives every job its own IANA timezone (like Europe/Berlin) and keeps “09:00 every weekday” at 09:00 local, all year.

Try it

Paste any expression into the cron expression explainer to see it in plain English and preview the next runs in your timezone — then schedule it on SteadyCron with retries and alerts.