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.
On this page
- What a cron expression actually is
- Four symbols do all the work
- How to read any cron expression
- Writing your own, step by step
- The day-of-month vs. day-of-week trap
- Time zones and daylight saving time
- Not every cron dialect is the same
- Common mistakes to avoid
- A few habits that pay off
- Frequently Asked Questions
- The short version
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.
┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12 or JAN–DEC)
│ │ │ │ ┌───── day of week (0–6, Sunday = 0)
│ │ │ │ │
* * * * *| Field | Allowed values | Example | Meaning |
|---|---|---|---|
| Minute | 0–59 | 30 | At half past the hour |
| Hour | 0–23 | 14 | During the 2 p.m. hour |
| Day of month | 1–31 | 1 | On the first of the month |
| Month | 1–12 or JAN–DEC | 6 | In June |
| Day of week | 0–6 (Sunday = 0) or SUN–SAT | 1 | On 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
Four symbols do all the work
Beyond plain numbers, standard cron only has four modifiers.
*— every value in the field.,— a list:1,15in the day-of-month field means the 1st and the 15th.-— a range:9-17in the hour field means 9 a.m. through 5 p.m., inclusive./— a step:*/10in 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 * *:
- Minute
30— at minute 30. - Hour
6— during the 6 a.m. hour. - Day of month
1— on the 1st. - Month
*— in every month. - 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:
| Expression | Runs |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour, on the hour |
0 0 * * * | Every day at midnight |
0 9 * * 1-5 | Weekdays at 9:00 a.m. |
0 */6 * * * | Every 6 hours (00:00, 06:00, 12:00, 18:00) |
15 14 * * 3 | Wednesdays at 2:15 p.m. |
0 0 * * 0 | Sundays 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.
- Say the schedule in plain words first: "at 7:30 in the morning, Monday through Friday."
- Minute:
30. - Hour:
7. - Day of month and month: no restriction, so
*and*. - 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:
# Runs every Friday; exits unless it is also the 13th
0 0 * * 5 [ "$(date +\%d)" = "13" ] && /usr/local/bin/monthly-report.shOne 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:
| Platform | Fields | Notable differences |
|---|---|---|
| Linux crontab (Vixie cron) | 5 | @daily-style shortcuts; 0 and 7 both mean Sunday |
| Quartz (Java, Spring) | 6–7 | Leading seconds field; extra ?, L, W, # symbols; day-of-week runs 1–7 |
| AWS EventBridge | 6 | Trailing year field; requires ? in one of the day fields |
| GitHub Actions | 5 | Standard 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
| Mistake | What actually happens | Fix |
|---|---|---|
* 9 * * * for "9 a.m. daily" | Runs every minute from 9:00 to 9:59 | 0 9 * * * |
| Swapping minute and hour | 8 15 * * * runs at 3:08 p.m., not 8:15 a.m. | Minute is always the first field |
| Restricting both day fields | 0 0 13 * 5 runs on Fridays and on the 13th | Restrict one field; check the other in the script |
Using 24 for midnight | Invalid — hours run 0–23 | Use 0 |
*/2 in day-of-month for "every other day" | The gap resets at each month boundary, so the rhythm breaks | Schedule fixed dates like 1,15, or handle the interval in code |
| Trusting the shell environment | Cron runs with a minimal environment; your profile is not loaded | Use 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
17or43competes 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?
How do I run a cron job every 5 minutes?
*/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?
sleep — but first consider whether the task really needs it.Why does my cron job run at the wrong time?
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?
SUN) is the safer, more portable choice.Do cron jobs catch up if the machine was off?
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.
Hands on
Tools mentioned in this article
Cron Expression Generator
Build cron expressions from plain scheduling choices and preview when jobs should run before adding them to servers or apps.
Timezone Converter
Convert meeting times across time zones and compare cities so calls, launches, deadlines, and travel plans line up correctly.
Regex Tester
Test regular expressions against sample text, review matches, tweak patterns, and debug JavaScript-style regex behavior faster.
Business Days Calculator
Count working days between dates while excluding weekends, useful for delivery estimates, notice periods, projects, and SLA planning.
More guides
Keep reading

What is JSON and how to format it correctly
A practical guide to JSON syntax, the mistakes that break it, and how a formatter turns a wall of text into something you can actually debug.

How to calculate EMI on any loan
The EMI formula explained in plain terms, a worked example, and what actually happens to your money over the life of a loan.

Password best practices in 2026
Why length beats complexity, how passphrases work, and a realistic system for managing passwords without losing your mind.
Put it into practice
Every guide comes with free tools to match.
No signup, no paywalls. Local tools run in your browser, and upload or AI steps are labeled clearly.