Cron Expressions Explained: Syntax, Examples & Pitfalls
September 26, 2026 · DevOps
Five little fields, endless confusion: */15 9-17 * * 1-5 looks like line noise until you learn to read it — then it's obvious. Cron expressions schedule recurring jobs on virtually every Unix system and in countless schedulers (CI pipelines, cloud cron, application frameworks). This guide teaches you to read and write them confidently, including the two traps that bite everyone exactly once.
What a cron expression is and where you'll meet one
A cron expression is a compact schedule: "run this at these times". The cron daemon on Linux/Unix reads them from crontabs (per-user schedule files, edited with crontab -e). But the syntax escaped Unix long ago — you'll meet it in GitHub Actions (on.schedule), AWS EventBridge, Kubernetes CronJobs, Jenkins, and Node libraries like node-cron. Dialects vary slightly (more on that later), but the five-field core is universal.
The five fields, left to right
┌──────────── minute (0–59)
│ ┌────────── hour (0–23)
│ │ ┌──────── day of month (1–31)
│ │ │ ┌────── month (1–12)
│ │ │ │ ┌──── day of week (0–7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *
Read narrowest to widest: which minute, of which hour, on which day. Each field is a set of allowed values; the job runs when all fields match the current time (with one famous exception — keep reading).
Special characters: *, ,, -, /
| Char | Meaning | Example |
|---|---|---|
* | Every possible value | * * * * * — every minute |
, | List of values | 0 9,17 * * * — 9:00 and 17:00 daily |
- | Range | 0 9-17 * * * — hourly, 9am–5pm |
/ | Step | */15 * * * * — every 15 minutes |
They compose: 0 9-17/2 * * 1-5 means "every 2 hours from 9 to 17, Monday to Friday" — 9:00, 11:00, 13:00, 15:00, 17:00 on weekdays. Month and weekday fields also accept names (JAN–DEC, MON–SUN) in most implementations, which reads better than numbers.
Reading real examples
| Expression | Meaning |
|---|---|
0 9 * * 1-5 | 9:00 every weekday |
*/15 * * * * | Every 15 minutes |
0 0 1 * * | Midnight on the 1st of each month |
30 2 * * 0 | 2:30 every Sunday morning |
0 8 * * 1 | 8:00 every Monday |
45 23 * * 5 | 23:45 every Friday |
Build it visually: describe your schedule in plain English with our free Cron Expression Generator and get the expression plus a human-readable explanation.
The two traps everyone hits once
Trap 1: 5 * * * * vs */5 * * * *. The first means "at minute 5 of every hour" — once per hour. The second means "every 5 minutes". If your "every 5 minutes" job mysteriously runs hourly, you wrote the first one. The */n step syntax is what divides the hour; a bare n is a single minute.
Trap 2: day-of-month OR day-of-week. When both day fields are restricted, cron runs when either matches — not both. 0 0 1 * 1 runs on the 1st of every month and every Monday. There is no standard-cron way to say "Monday the 1st"; you need the job itself to check the date, or a smarter scheduler. Tattoo this on your brain: days are OR, everything else is AND.
Special strings
| String | Equivalent |
|---|---|
@reboot | Run once at startup |
@hourly | 0 * * * * |
@daily | 0 0 * * * (midnight) |
@weekly | 0 0 * * 0 |
@monthly | 0 0 1 * * |
@yearly | 0 0 1 1 * |
These are conveniences, not magic — @daily is exactly 0 0 * * *. Note @reboot doesn't take fields at all; it's "run this command once when the daemon starts".
Dialects and gotchas
- Quartz / Java schedulers use 6–7 fields (seconds first, optional year last):
0 */5 * * * ?. The?means "no specific value" and exists to dodge the day-of-month/day-of-week OR trap. - AWS EventBridge / CloudWatch cron also has 6 fields and requires
?in one of the day fields. - Timezone: cron uses the system local timezone. A server in UTC running your "9am" job at 9am UTC is a classic incident. Some crons honor a
TZ=line; otherwise do the conversion yourself. - The environment trap: cron runs with a minimal environment — tiny
PATH, no shell profile, no aliases. A script that works in your terminal but fails in cron is almost always missing an absolute path or an env var. Always use full paths and redirect output to a log:*/5 * * * * /usr/local/bin/job.sh >> /var/log/job.log 2>&1. - Overlapping runs: if a job takes longer than its interval, cron happily starts a second copy. Use a lock file (
flock) for anything that must not overlap.
Frequently asked questions
- What does */5 * * * * mean?
- "Every 5 minutes." The */n syntax means "every nth unit" starting at 0 — so minutes 0, 5, 10, 15, and so on. Don't confuse it with `5 * * * *`, which means "at 5 minutes past every hour" (once per hour).
- How do I run a cron job every 5 minutes?
- Use `*/5 * * * *`. The step syntax in the minute field divides the hour into 5-minute slots. If you need it offset (e.g. at :2, :7, :12 past), write `2-57/5 * * * *`.
- What are the 5 fields in cron?
- Minute (0–59), hour (0–23), day of month (1–31), month (1–12), day of week (0–7, where both 0 and 7 mean Sunday). They're read left to right, narrowest time unit to widest.
- Why did my cron job not run?
- The usual suspects: the cron daemon isn't running or the crontab wasn't installed for the right user; the command relied on PATH or environment variables that don't exist in cron's minimal environment (use absolute paths); a typo in the schedule; or the script needs a shell profile that cron never loads. Redirect output to a log file so failures aren't silent.
- What's the difference between day of month and day of week?
- When both fields are restricted (not `*`), cron treats them as OR, not AND: `0 0 1 * 1` runs on the 1st of the month AND every Monday — not "Monday the 1st". This is the most misunderstood cron behavior there is.
- Can cron run every second?
- No — the smallest cron unit is one minute. For sub-minute scheduling, run a script every minute that loops internally, or use a proper scheduler (systemd timers support sub-minute accuracy, as do most job queues).
- What timezone does cron use?
- The system's local timezone by default — which is whatever the server was configured with, a classic source of "it ran at the wrong time" bugs. Some cron implementations allow a TZ variable in the crontab; otherwise, convert your intended time to the server's timezone explicitly.
Related articles
HTTP Security Headers Explained
What Content-Security-Policy, HSTS, X-Frame-Options and friends actually do — and the minimal set every site should ship.
SecurityWhat Is a JWT? Structure, Claims & Security Explained
JSON Web Tokens explained: header, payload, and signature, what claims like exp and sub mean, and the security rules that actually matter.
Web DevelopmentBase64 Encoding Explained: How It Works & When to Use It
What base64 really does, why it inflates data by 33%, base64 vs base64url, and when to reach for it — and when not to.