Cron Syntax Explained: Five Fields and the Traps That Break Schedules
A cron expression is five fields that say when a job should run. That's easy to learn and surprisingly easy to get wrong. The bugs don't come from typos. They come from rules that aren't obvious: steps that don't divide evenly, a day-of-month and day-of-week combination that means "or" instead of "and", and schedules that quietly run in UTC when you meant local time. This guide covers the syntax, then works through the traps with real dates.
The five fields
┌───────────── minute (0 to 59)
│ ┌─────────── hour (0 to 23)
│ │ ┌───────── day of month (1 to 31)
│ │ │ ┌─────── month (1 to 12)
│ │ │ │ ┌───── day of week (0 to 6, Sunday = 0; many crons also accept 7)
│ │ │ │ │
* * * * * commandEach field accepts a few kinds of value. * means every value. A number means exactly that value. 1-5 is a range. 1,15 is a list. */15 is a step: every 15th value starting from the lowest. You can combine them, so 0-30/10 means 0, 10, 20 and 30.
| Expression | Meaning | Runs per day |
|---|---|---|
* * * * * | Every minute | 1,440 |
*/15 * * * * | Every 15 minutes, on :00, :15, :30, :45 | 96 |
0 * * * * | On the hour, every hour | 24 |
30 2 * * * | Daily at 02:30 | 1 |
0 9 * * 1-5 | 09:00 Monday to Friday | 1 on weekdays |
0 0 1 * * | Midnight on the 1st of each month | 1 on the 1st |
0 */6 * * * | 00:00, 06:00, 12:00, 18:00 | 4 |
The weekday schedule 0 9 * * 1-5 runs 261 times in 2026, once on each weekday. Cron knows nothing about public holidays, so it'll fire on Diwali and Christmas unless your script checks for them.
Trap 1: steps that don't divide evenly
A step restarts at the beginning of each cycle. It doesn't carry over. Look at */7 * * * *, which people often read as "every seven minutes". The minute values it matches are 0, 7, 14, 21, 28, 35, 42, 49 and 56. After :56, the next run is :00 of the next hour, only four minutes later. So you get eight seven-minute gaps and one four-minute gap every hour, and 9 runs per hour instead of the 8.57 you'd expect from true seven-minute spacing.
The same applies to hours. 0 */5 * * * runs at 00:00, 05:00, 10:00, 15:00 and 20:00, then 00:00 again after only four hours. If you need even spacing, choose a step that divides 60 (for minutes) or 24 (for hours): 1, 2, 3, 4, 5, 6, 10, 12, 15, 20 or 30 for minutes; 1, 2, 3, 4, 6, 8 or 12 for hours. For anything else, cron isn't the right tool; use a scheduler that supports fixed intervals.
Trap 2: day-of-month OR day-of-week
This is the rule that surprises experienced engineers. If both the day-of-month and day-of-week fields are restricted (neither is *), standard cron runs the job when either one matches, not when both do.
So 0 0 13 * 5 doesn't mean "Friday the 13th". It means "every 13th of the month, and also every Friday". Let's count for 2026: there are 52 Fridays and 12 thirteenths, and three of those thirteenths (February, March and November) fall on a Friday. The job runs 52 + 12 − 3 = 61 times. If you wanted only Friday the 13th, it'd run 3 times. That's a 20-fold difference from one misunderstanding.
To get "and" behaviour, restrict one field in cron and check the other in the command. For Friday the 13th: 0 0 13 * * and then have the script exit unless the day is a Friday. For "the first Monday of the month": 0 9 1-7 * * and have the script check that it's Monday.
Trap 3: which time zone?
Classic cron uses the server's system time zone. Many cloud servers are set to UTC. Hosted schedulers often use UTC too. GitHub Actions schedules, for instance, always run in UTC. A job you meant to run at 09:00 in India, written as 0 9 * * *, would run at 14:30 IST on a UTC machine, because IST is UTC+5:30.
Daylight saving time adds a second problem in countries that use it. When clocks go forward, a time like 02:30 may not exist that day. When they go back, 01:30 can happen twice. Different cron implementations handle this differently: some skip, some run once, some run twice. If a job must run exactly once a day in a DST zone, schedule it outside the changeover hours or run the server in UTC. India doesn't observe DST, so IST schedules don't have this problem, but a server set to a European or US zone does.
Trap 4: cron's environment isn't your shell
- PATH is minimal. A command that works in your terminal can fail under cron because it isn't found. Use absolute paths, like
/usr/bin/python3, or set PATH at the top of the crontab. - % is special in crontab lines. An unescaped
%is turned into a newline. A command likedate +%Y-%m-%dmust be written asdate +\%Y-\%m-\%dinside a crontab. - Output goes nowhere useful. By default cron tries to email output to the user. On most servers that mail is never read. Redirect output to a log:
>> /var/log/myjob.log 2>&1. - Jobs can overlap. If a job scheduled every 5 minutes sometimes takes 7, two copies run at once. Use a lock, such as
flock, if that would be a problem.
Other cron dialects
"Cron syntax" isn't one standard. The five-field form above is what Linux crontabs use. Others differ:
| System | Fields | Notable differences |
|---|---|---|
| Linux crontab (Vixie, cronie) | 5 | Supports shortcuts like @daily and @reboot |
| Quartz (Java), Spring | 6 or 7 | Seconds come first; uses ?, L, W and # |
| AWS EventBridge | 6 | Adds a year field; ? required in day-of-month or day-of-week |
| GitHub Actions | 5 | Always UTC; very frequent schedules can be delayed under load |
| Kubernetes CronJob | 5 | Recent versions accept a time zone setting |
Paste a five-field expression into a Quartz scheduler and it'll be read as starting with seconds, which shifts every field one place. Always check which dialect a system expects.
Building and checking expressions
The Cron Expression Generator builds standard five-field expressions from simple controls, describes the schedule in plain English, and lists the next few run times. A reliable workflow:
- Build the expression or paste one in.
- Read the plain-English description and make sure it says what you meant.
- Check the next run times, keeping in mind they're shown in your browser's time zone.
- Convert to the server's time zone if it differs. The Time Zone Converter helps with the offset.
- For any expression that restricts both day fields, check that the preview lists the extra days the OR rule adds. Paste
0 0 13 * 5to see it list every Friday as well as the 13th.
Limitations of the generator
- The visual builder covers the common cases only. It builds single values, lists, ranges and steps. Pasted expressions that mix them, such as
1-30/5or1-5,7, or that use names likeMON, are kept exactly as written and previewed correctly, but you can't edit them field by field without replacing them. - Upcoming runs use your device's clock, not your server's.
- The preview follows standard rules only. It matches Vixie-style Linux cron, including the OR rule for the two day fields. Other schedulers with different rules may run at different times.
- Only five-field expressions work. Quartz and EventBridge expressions with seconds or years aren't supported, and shortcuts like
@dailyaren't accepted.
The preview looks up to five years ahead. An expression that can never fire, such as 0 0 30 2 * (30 February), shows no upcoming runs at all, which is a useful sign that something is wrong.
Build a cron schedule
Turn a schedule into a valid five-field expression, read it back in plain English, and preview when it'll run.