Skip to content
motifuse
Guide
11 min read

Cron Expressions Explained: How to Read and Write Cron Schedules

Learn how the five cron fields work, how to read any cron expression, and how to write schedules that fire when you expect — plus classic mistakes to avoid.

The motifuse team

Every scheduled job you depend on — the nightly database backup, the weekly summary email, the CI pipeline that runs while you sleep — is usually driven by a short string like 0 2 * * *. That string is a cron expression, and it has a reputation for being unreadable: five fields, a pile of asterisks, and no obvious hint about which number means what.

The reputation is undeserved. Cron syntax is one of the smallest languages you will ever learn: five positions, four symbols, and two or three genuine gotchas. Once you can read one expression, you can read them all.

This guide walks through the five fields, the symbols that modify them, worked examples you can adapt, the classic mistakes — including the day-of-week trap that surprises almost everyone — and what changes when time zones and daylight saving get involved.

What a cron expression actually is

Cron is the standard job scheduler on Linux and other Unix-like systems, and its schedule format has spread far beyond it: CI pipelines, cloud schedulers, and application frameworks all borrow the same syntax. You give the scheduler a table of jobs where each entry pairs a schedule with a command. The schedule part is the cron expression: five fields separated by spaces, read left to right.

text
┌───────────── minute (059)
│ ┌─────────── hour (023)
│ │ ┌───────── day of month (131)
│ │ │ ┌─────── month (112 or JAN–DEC)
│ │ │ │ ┌───── day of week (06, Sunday = 0)
│ │ │ │ │
* * * * *
FieldAllowed valuesExampleMeaning
Minute0–5930At half past the hour
Hour0–2314During the 2 p.m. hour
Day of month1–311On the first of the month
Month1–12 or JANDEC6In June
Day of week0–6 (Sunday = 0) or SUNSAT1On Mondays

An asterisk means "every value," so * * * * * runs every minute of every day. A job fires when the current time matches all five fields — with one important exception for the two day fields, covered below.

If you would rather build a schedule by picking options and reading the result back in plain English, the Cron Expression Generator does exactly that, right in your browser.

Try it right here

Cron Expression Generator

Open full tool
Loading embedded tool...

Four symbols do all the work

Beyond plain numbers, standard cron only has four modifiers.

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

Symbols combine. 9-17/2 in the hour field means every second hour from 9 to 17 — that is 9, 11, 13, 15, and 17 — so 0 9-17/2 * * 1-5 runs at the top of those hours, Monday to Friday.

How to read any cron expression

Read one field at a time and say it out loud. Take 30 6 1 * *:

  1. Minute 30 — at minute 30.
  2. Hour 6 — during the 6 a.m. hour.
  3. Day of month 1 — on the 1st.
  4. Month * — in every month.
  5. Day of week * — no weekday restriction.

Put together: at 6:30 a.m. on the first day of every month.

These ten schedules cover most real-world needs:

ExpressionRuns
* * * * *Every minute
*/5 * * * *Every 5 minutes
0 * * * *Every hour, on the hour
0 0 * * *Every day at midnight
0 9 * * 1-5Weekdays at 9:00 a.m.
0 */6 * * *Every 6 hours (00:00, 06:00, 12:00, 18:00)
15 14 * * 3Wednesdays at 2:15 p.m.
0 0 * * 0Sundays at midnight
0 0 1 * *First day of every month at midnight
0 0 1 1 *January 1st at midnight

Many cron implementations also accept shortcuts like @hourly, @daily, @weekly, @monthly, and @reboot. They are convenient, but they are extensions rather than part of the original five-field syntax, so check that your platform supports them before relying on one.

Writing your own, step by step

Suppose you want a report generated every weekday at 7:30 a.m.

  1. Say the schedule in plain words first: "at 7:30 in the morning, Monday through Friday."
  2. Minute: 30.
  3. Hour: 7.
  4. Day of month and month: no restriction, so * and *.
  5. Day of week: Monday through Friday is 1-5.

Result: 30 7 * * 1-5.

Working in this order — words first, fields second — protects you from the most common beginner bug: field order. The minute comes first, so 8:15 a.m. is written 15 8 * * *. Reversing it to 8 15 * * * is still a perfectly valid expression — it just runs at 3:08 p.m. instead, and nothing will warn you.

The day-of-month vs. day-of-week trap

This is the gotcha that catches even experienced users. When both day fields are restricted, standard cron treats them as either/or, not both.

Consider 0 0 13 * 5 — midnight, day-of-month 13, Friday. It looks like "Friday the 13th." It actually runs on every 13th of the month and on every Friday, which is four or five extra runs per month.

If you need true "Friday the 13th" behavior, restrict one field in cron and check the other inside the command:

bash
# Runs every Friday; exits unless it is also the 13th
0 0 * * 5 [ "$(date +\%d)" = "13" ] && /usr/local/bin/monthly-report.sh

One detail worth knowing if you copy that line into a real crontab: the % character is special there and has to be escaped as \%, as shown — an unescaped percent sign is treated as a line break.

Time zones and daylight saving time

A cron expression has no time zone of its own. It fires based on the clock of whatever system evaluates it: your server's local time for a classic crontab, UTC for GitHub Actions schedules, and a configurable zone on many managed platforms. The same 0 9 * * 1-5 means 9 a.m. in one place and an entirely different local time somewhere else.

Two practical consequences follow:

  • If your team or users span regions, decide the schedule in one reference zone and convert deliberately. The Timezone Converter shows what a given UTC time means locally, and our guide to working across time zones covers the scheduling side in depth.
  • Daylight saving transitions can skip or repeat an hour of local time. A job scheduled at 2:30 a.m. local time may not run on the spring-forward night, or may run twice in autumn, depending on your cron implementation. If a job must run exactly once per day, schedule it outside the transition window or run the system on UTC.

Not every cron dialect is the same

The five-field format described here is the widely shared core, but schedulers extend it in different directions:

PlatformFieldsNotable differences
Linux crontab (Vixie cron)5@daily-style shortcuts; 0 and 7 both mean Sunday
Quartz (Java, Spring)6–7Leading seconds field; extra ?, L, W, # symbols; day-of-week runs 1–7
AWS EventBridge6Trailing year field; requires ? in one of the day fields
GitHub Actions5Standard syntax, always evaluated in UTC

The practical advice: write and test the standard five fields first, then adapt to your platform's dialect by reading its documentation. An expression written for Quartz will not necessarily behave the same way in a Linux crontab, or vice versa.

Common mistakes to avoid

MistakeWhat actually happensFix
* 9 * * * for "9 a.m. daily"Runs every minute from 9:00 to 9:590 9 * * *
Swapping minute and hour8 15 * * * runs at 3:08 p.m., not 8:15 a.m.Minute is always the first field
Restricting both day fields0 0 13 * 5 runs on Fridays and on the 13thRestrict one field; check the other in the script
Using 24 for midnightInvalid — hours run 0–23Use 0
*/2 in day-of-month for "every other day"The gap resets at each month boundary, so the rhythm breaksSchedule fixed dates like 1,15, or handle the interval in code
Trusting the shell environmentCron runs with a minimal environment; your profile is not loadedUse absolute paths and redirect output to a log

The last row causes the classic "works in my terminal, fails in cron" mystery. When a cron job misbehaves, the schedule is often fine — the command simply cannot find what it needs. Redirecting output to a log file (>> /var/log/myjob.log 2>&1) turns that mystery into a readable error message.

A few habits that pay off

  • Keep a comment above every crontab line saying what the job does and why the schedule was chosen. A bare list of expressions ages badly.
  • Test with a fast schedule first. Run a new job on */2 * * * * for ten minutes, confirm the log looks right, then switch to the real schedule.
  • Avoid the top of the hour for heavy jobs when you control the timing. Everyone schedules at :00; a job at minute 17 or 43 competes with far less.
  • Note the time zone assumption next to the schedule, especially in CI configuration files that run in UTC.
  • One job, one line, one log file. Chained mega-commands in a single entry are hard to debug at 2 a.m. — which is exactly when you will be debugging them.

If you spend much time on this kind of plumbing, the rest of our developer tools cover the neighboring chores — for example, checking a pattern in the Regex Tester before it goes into a log-parsing job. And remember that cron counts calendar days, not working days. For "5 business days from now" logic, reach for the Business Days Calculator instead, because no cron field knows about weekends and holidays.

Frequently Asked Questions

What do the five fields in a cron expression mean?
In order: minute (0–59), hour (0–23), day of month (1–31), month (1–12), and day of week (0–6, where 0 is Sunday). An asterisk in any position means "every value." The job runs when the current time matches the fields, with day-of-month and day-of-week combined as either/or when both are restricted.
How do I run a cron job every 5 minutes?
Use */5 * * * *. The step symbol / counts from the start of the minute range, so it fires at :00, :05, :10, and so on — twelve times per hour.
Can cron run a job more often than once a minute?
Not in standard cron; one minute is the smallest unit. If you genuinely need sub-minute scheduling, the usual options are a systemd timer, a loop inside a long-running process, or multiple cron entries offset with sleep — but first consider whether the task really needs it.
Why does my cron job run at the wrong time?
The two usual causes are field order and time zone. The minute comes first, so 8:15 a.m. is 15 8 * * *, not 8 15 * * *. And the job runs on the scheduler's clock, which may be UTC or a server zone rather than your local time. Translating the expression with the Cron Expression Generator catches the first problem; checking the host's time zone catches the second.
Is 7 a valid day-of-week value for Sunday?
In many implementations, yes — both 0 and 7 mean Sunday. But not every dialect accepts 7, so 0 (or the name SUN) is the safer, more portable choice.
Do cron jobs catch up if the machine was off?
No. Standard cron only fires at the scheduled moment; if the system was off or asleep, that run is skipped silently. Use anacron, a systemd timer with persistence enabled, or your own catch-up logic when missed runs matter.

The short version

A cron expression is five fields — minute, hour, day of month, month, day of week — plus four symbols: * for every, , for lists, - for ranges, and / for steps. Read them left to right, keep the either/or rule for the two day fields in mind, know which clock your scheduler uses, and test with a fast schedule before trusting a slow one. Build or double-check your next schedule with the Cron Expression Generator, and it will fire when you meant it to — not just when you wrote it to.

Put it into practice

Every guide comes with free tools to match.

Public tools open without sign-up. Local, upload, AI, and premium workspace steps are labeled clearly.

Explore all tools