Cron is the time-based job scheduler in Unix-like systems. Whether you're scheduling backups, sending reports, or running maintenance tasks, understanding cron expressions is essential.
What is Cron?
Cron runs scheduled commands at specified times. The schedule is defined using a cron expression—a string of five (or six) fields representing when to run.
A cron job entry looks like:
30 4 * * * /path/to/script.sh
This runs script.sh at 4:30 AM every day.
Cron Expression Format
The standard cron expression has five fields:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday = 0)
│ │ │ │ │
* * * * * command
Each field accepts:
- A specific value:
5 - A range:
1-5 - A list:
1,3,5 - A step:
*/15(every 15) - An asterisk:
*(any value)
Anatomy of a Cron Expression
A cron expression is made up of five space-separated fields. Reading left to right, each field controls one unit of time—from minutes up to the day of the week:
* * * * *
│ │ │ │ │
│ │ │ │ └── Day of week (0-6, Sunday = 0)
│ │ │ └──── Month (1-12)
│ │ └────── Day of month (1-31)
│ └──────── Hour (0-23)
└────────── Minute (0-59)
Here's the same breakdown as a quick-reference table:
| Position | Field | Allowed values | Special characters |
|---|---|---|---|
| 1 | Minute | 0-59 | * , - / |
| 2 | Hour | 0-23 | * , - / |
| 3 | Day of month | 1-31 | * , - / |
| 4 | Month | 1-12 (or JAN-DEC) |
* , - / |
| 5 | Day of week | 0-6 (or SUN-SAT, 0 = Sunday) |
* , - / |
When every field is *, the job runs every minute. You narrow the schedule by replacing an * with a value, range, list, or step. A simple mental model: read the fields in order as "run at minute X, hour Y, on day Z, in month M, on weekday W" and you'll know exactly when the job fires.
Worked Examples: Cron Expressions in Plain English
The fastest way to learn cron is to decode real expressions. Here are the most common ones, translated into plain English:
| Expression | What it means |
|---|---|
* * * * * |
Every minute |
*/5 * * * * |
Every 5 minutes |
*/15 * * * * |
Every 15 minutes (:00, :15, :30, :45) |
0 * * * * |
Every hour, on the hour (at minute 0) |
30 * * * * |
Every hour at 30 minutes past |
0 0 * * * |
Every day at midnight (00:00) |
0 9 * * * |
Every day at 9:00 AM |
0 9 * * 1-5 |
9:00 AM on weekdays (Monday to Friday) |
0 0 * * 0 |
Every Sunday at midnight |
0 0 1 * * |
Midnight on the 1st of every month |
15 14 1 * * |
2:15 PM on the 1st of every month |
0 22 * * 1-5 |
10:00 PM on every weekday |
Decoding */5 * * * * step by step
*/5 * * * * is one of the most-searched cron expressions. Here's what each field says:
*/5(minute) — every 5th minute: 0, 5, 10, 15 … 55*(hour) — every hour*(day of month) — every day*(month) — every month*(day of week) — every day of the week
Put together, it reads: "run every 5 minutes, all day, every day." Swap the step for */15 and you get the same idea every 15 minutes instead.
Special Strings (Cron Nicknames)
Most cron implementations (including Vixie cron and many CI/CD systems) accept convenient nicknames in place of the five fields. They're easier to read and harder to get wrong:
| Nickname | Equivalent | Meaning |
|---|---|---|
@hourly |
0 * * * * |
Every hour, on the hour |
@daily (or @midnight) |
0 0 * * * |
Every day at midnight |
@weekly |
0 0 * * 0 |
Every Sunday at midnight |
@monthly |
0 0 1 * * |
Midnight on the 1st of each month |
@yearly (or @annually) |
0 0 1 1 * |
Midnight on January 1st |
@reboot |
— | Once, at system startup |
For example, @daily /path/to/backup.sh is a clearer way to write 0 0 * * * /path/to/backup.sh. Note that @reboot has no time equivalent—it runs a single time whenever the machine boots, which is handy for starting services or one-off warm-up tasks.
Minutes, Hours, Days, Months, Weekdays
Minutes (0-59)
0 * * * * # At minute 0 (top of every hour)
30 * * * * # At minute 30
*/15 * * * * # Every 15 minutes (0, 15, 30, 45)
Hours (0-23)
0 9 * * * # At 9:00 AM
0 0 * * * # At midnight (0:00)
0 */2 * * * # Every 2 hours
0 9-17 * * * # Every hour from 9 AM to 5 PM
Day of Month (1-31)
0 0 1 * * # First day of every month
0 0 15 * * # 15th of every month
0 0 1,15 * * # 1st and 15th of every month
Month (1-12 or JAN-DEC)
0 0 1 1 * # January 1st
0 0 1 */3 * # First day of every quarter
0 0 1 6,12 * # June 1st and December 1st
Day of Week (0-6 or SUN-SAT)
0 0 * * 0 # Every Sunday
0 0 * * 1-5 # Monday through Friday
0 0 * * 6,0 # Weekends (Saturday and Sunday)
Special Characters
Asterisk (*) - Any Value
* * * * * # Every minute of every hour of every day
0 * * * * # Every hour (at minute 0)
Slash (/) - Step Values
*/5 * * * * # Every 5 minutes
0 */3 * * * # Every 3 hours
0 0 */2 * * # Every 2 days
Dash (-) - Ranges
0 9-17 * * * # Every hour from 9 to 17 (9 AM - 5 PM)
0 0 * * 1-5 # Monday through Friday
Comma (,) - Lists
0 0,12 * * * # Midnight and noon
0 0 1,15 * * # 1st and 15th
0 0 * * 0,3,6 # Sunday, Wednesday, Saturday
Common Cron Patterns
Every Hour
0 * * * *
Runs at minute 0 of every hour (1:00, 2:00, 3:00...).
Daily at Midnight
0 0 * * *
Every Monday at 9 AM
0 9 * * 1
First of Every Month at Midnight
0 0 1 * *
Every Weekday at 8:30 AM
30 8 * * 1-5
Every 15 Minutes
*/15 * * * *
Twice Daily (9 AM and 6 PM)
0 9,18 * * *
Every Sunday at 3 AM
0 3 * * 0
Last Day of Month
Cron doesn't support "last day" directly. Workarounds:
# Run on days 28-31, but check if it's the last day
0 0 28-31 * * [ "$(date +\%d -d tomorrow)" = "01" ] && /script.sh
Cron vs Cron-like (6 vs 5 Fields)
Some systems (like Quartz, Spring) use 6 fields, adding seconds:
┌───────────── second (0 - 59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12)
│ │ │ │ │ ┌───────────── day of week (0 - 6)
│ │ │ │ │ │
* * * * * * command
Spring and Quartz may also support:
?for "no specific value" in day fieldsLfor "last" (last day of month, last Friday)Wfor weekday nearest to given date#for "nth weekday" (e.g.,2#1= first Monday)
Always check which cron flavor your system uses!
Timezone Considerations
Cron times are in the system's local timezone by default.
Problems This Causes
- DST transitions: Jobs may run twice or skip during daylight saving changes
- Server relocation: Moving servers to different timezones affects schedules
- Distributed systems: Servers in different timezones run at different actual times
Solutions
Use UTC where possible:
CRON_TZ=UTC 0 0 * * * /script.sh # Runs at midnight UTCDocument the timezone clearly in comments
Avoid scheduling during DST transitions (2-3 AM)
Testing Cron Expressions
Before deploying, verify your expression:
Manual Verification
Walk through the expression field by field:
30 4 1 * *
│ │ │ │ └─ Any day of week
│ │ │ └─── Any month
│ │ └───── 1st day of month
│ └─────── 4:00 (hour)
└────────── 30 minutes
= "At 4:30 AM on the 1st of every month"
Use Testing Tools
Rather than decoding fields by hand, paste your expression into our Cron Expression Builder—it will explain the cron expression in plain English, flag syntax errors, and list the upcoming run times. Online cron expression testers generally show:
- Human-readable explanation
- Next N scheduled runs
- Validation errors
Common Mistakes
1. Forgetting Day of Week is 0-6
0 0 * * 7 # WRONG - some systems don't accept 7
0 0 * * 0 # RIGHT - Sunday is 0
2. Day of Month AND Day of Week
Most crons run if EITHER matches:
0 0 15 * 5 # Runs on the 15th AND every Friday
This probably isn't what you intended!
3. Running Too Frequently
* * * * * # Every minute - probably too often!
Consider: Do you really need this frequency? What about overlap if the job takes >1 minute?
4. No Error Handling
Cron jobs can fail silently. Always:
- Log output and errors
- Set up monitoring
- Handle failures gracefully
0 * * * * /script.sh >> /var/log/script.log 2>&1
5. Missing PATH
Cron uses a minimal environment:
# Set PATH explicitly
PATH=/usr/local/bin:/usr/bin:/bin
0 * * * * my-command
Or use full paths:
0 * * * * /usr/local/bin/my-command
Advanced Tips
Multiple Schedules
Run at multiple times with separate lines:
0 9 * * * /script.sh # 9 AM
0 17 * * * /script.sh # 5 PM
Predefined Schedules
Many systems support shortcuts:
| Shortcut | Equivalent |
|---|---|
@yearly |
0 0 1 1 * |
@monthly |
0 0 1 * * |
@weekly |
0 0 * * 0 |
@daily |
0 0 * * * |
@hourly |
0 * * * * |
@reboot |
Run once at startup |
Lock to Prevent Overlap
Use flock to prevent concurrent runs:
* * * * * /usr/bin/flock -n /tmp/script.lock /path/to/script.sh
Frequently Asked Questions
What is a cron expression?
A cron expression is a compact string of five fields—minute, hour, day of month, month, and day of week—that tells the cron scheduler when to run a command. For example, 0 9 * * 1-5 means "at 9:00 AM, Monday through Friday." It's the standard way to describe repeating schedules on Unix-like systems, and the same syntax appears in Kubernetes CronJobs, CI/CD pipelines, and many programming frameworks.
What does */5 * * * * mean?
*/5 * * * * means "run every 5 minutes." The */5 in the first (minute) field is a step value that matches minute 0, 5, 10, 15, and so on up to 55. The remaining four * fields mean "every hour, every day, every month, every day of the week"—so the job fires 12 times an hour, around the clock.
What do the five fields in cron mean?
Reading left to right, the five fields are minute (0-59), hour (0-23), day of month (1-31), month (1-12), and day of week (0-6, where 0 is Sunday). Each field can hold a specific value, a range (1-5), a list (1,3,5), a step (*/15), or * for "any value." See the Anatomy of a Cron Expression table above for the full breakdown.
What is the difference between * and */1?
Functionally, nothing—they produce the same schedule. * means "every valid value" for that field, and */1 means "every value, stepping by 1," which is also every value. In the minute field, both * * * * * and */1 * * * * run every minute. Prefer plain * because it's the conventional and more readable form.
How do I write a cron job that runs every hour?
Use 0 * * * *, which means "at minute 0 of every hour"—so it fires at 1:00, 2:00, 3:00, and so on. The leading 0 matters: writing * * * * * would run the job every minute, not once an hour. Many systems also accept the nickname @hourly as a drop-in equivalent.
Summary
Cron expressions are powerful once you understand them:
- 5 fields: minute, hour, day, month, weekday
- Special characters:
*(any),/(step),-(range),,(list) - Watch for gotchas: timezones, DST, day-of-month vs day-of-week
- Test before deploying: Use tools to verify your expression
- Monitor your jobs: Log output and handle failures
Ready to test a cron expression? Our Cron Expression Builder will explain any cron expression in plain, human-readable English, validate your syntax, and show the next scheduled run times—paste in something like */5 * * * * and it tells you exactly when it fires.