Cron expressions look intimidating—five fields of asterisks, slashes, and numbers that somehow encode "run every Tuesday at 3:30 AM." But once you understand the structure, reading and writing cron schedules becomes straightforward.
This guide breaks down cron expression syntax from the ground up, covers the patterns you'll actually use in production, and highlights the one gotcha that catches almost everyone.
The Five Fields
A standard cron expression has five space-separated fields:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12 or JAN-DEC)
│ │ │ │ ┌───────────── day of week (0-7 or SUN-SAT)
│ │ │ │ │
* * * * *
Each field accepts:
| Syntax | Meaning | Example |
|--------|---------|---------|
| * | Every value | * * * * * = every minute |
| 5 | Specific value | 5 * * * * = at minute 5 |
| 1,15,30 | List | 0 9,17 * * * = 9 AM and 5 PM |
| 1-5 | Range | 0 9 * * 1-5 = Mon through Fri |
| */5 | Step | */5 * * * * = every 5 minutes |
| 10-30/5 | Range + step | 10-30/5 * * * * = at minutes 10, 15, 20, 25, 30 |
Note: Day of week uses 0-7 where both 0 and 7 mean Sunday. You can also use three-letter names: SUN, MON, TUE, WED, THU, FRI, SAT.
Common Patterns You'll Actually Use
Here are the cron schedule examples that cover 90% of real-world needs:
Every 5 minutes:
*/5 * * * *
The step value */5 means "starting at 0, every 5th value." This fires at :00, :05, :10, :15, and so on.
Every weekday at 9:00 AM:
0 9 * * 1-5
Minute 0, hour 9, any day of month, any month, Monday through Friday.
First day of every month at midnight:
0 0 1 * *
Minute 0, hour 0, day 1, any month, any day of week.
Every Sunday at 2:30 AM:
30 2 * * 0
Every 15 minutes during business hours (9 AM to 5 PM), weekdays only:
*/15 9-17 * * 1-5
This fires at :00, :15, :30, :45 for each hour from 9 through 17, Monday to Friday. Note that it also fires at 17:15, 17:30, and 17:45—if you want to stop exactly at 17:00, you'd need two expressions or a more specific hour range like 9-16 plus 0 17 * * 1-5.
Every hour at the 30-minute mark:
30 * * * *
Twice daily (6 AM and 6 PM):
0 6,18 * * *
Every Monday, Wednesday, and Friday at 8 AM:
0 8 * * 1,3,5
The Day-of-Month vs Day-of-Week Gotcha
This is the one that bites people in production. When you specify both the day-of-month field and the day-of-week field (i.e., neither is *), cron uses OR logic, not AND.
0 0 1 * 5
You might read this as "run at midnight on the 1st of the month, but only if it's a Friday." That's wrong. It actually means: "run at midnight on the 1st of the month OR on any Friday."
This happens because the original Vixie cron implementation treats restricted day-of-month and day-of-week as independent triggers. If either condition is met, the job runs.
How to get AND behavior:
If you truly need "the 1st of the month, but only if it's a Friday," you have two options:
- Use one field and handle the other in your script:
0 0 1 * *
Then in your script:
#!/bin/bash
# Only proceed if today is Friday
if [ "$(date +%u)" -ne 5 ]; then
exit 0
fi
# ... actual job logic
- Use a cron implementation that supports AND logic. Some modern schedulers (like Quartz in Java) handle this differently. Check your platform's documentation.
The safe rule: If you need day-of-week filtering, set day-of-month to *. If you need day-of-month filtering, set day-of-week to *. Avoid restricting both simultaneously unless you understand the OR behavior.
Step Values, Ranges, and Lists in Depth
Step values (/) define the interval between matches:
*/10 * * * * → minutes 0, 10, 20, 30, 40, 50
0 */6 * * * → hours 0, 6, 12, 18
0 0 */2 * * → every 2nd day of the month (1st, 3rd, 5th...)
A common misconception: */5 in the minute field does NOT mean "every 5 minutes starting from when the job was created." It means "at minutes divisible by 5." If you create the job at 10:03, the next run is 10:05, not 10:08.
Ranges (-) define a continuous span:
0 9-17 * * * → every hour from 9 AM to 5 PM (inclusive)
0 0 * * 1-5 → midnight, Monday through Friday
0 0 1-15 * * → first 15 days of each month
Lists (,) combine discrete values:
0 8,12,18 * * * → 8 AM, noon, and 6 PM
0 0 1,15 * * → 1st and 15th of each month
You can combine all three:
0,30 9-17/2 * * 1-5
This means: at minutes 0 and 30, for hours 9, 11, 13, 15, 17, Monday through Friday.
Real-World Examples: CI/CD and Backups
CI/CD: Run tests every 15 minutes during development hours:
*/15 8-20 * * 1-5
Fires every 15 minutes from 8 AM to 8 PM, weekdays. Your CI pipeline picks up new commits and runs the test suite without burning compute overnight.
Database backup: Daily at 2 AM:
0 2 * * *
Simple and reliable. Pair it with a retention script:
#!/bin/bash
pg_dump mydb | gzip > /backups/mydb_$(date +%Y%m%d).sql.gz
find /backups -name "*.sql.gz" -mtime +30 -delete
Weekly backup: Sunday at 3 AM:
0 3 * * 0
Monthly report generation: 1st of month at 6 AM:
0 6 1 * *
Certificate renewal check: Every day at 4 AM (for Let's Encrypt 90-day certs):
0 4 * * *
With a script that only renews if expiry is within 30 days:
#!/bin/bash
certbot renew --quiet --deploy-hook "systemctl reload nginx"
Log rotation: Every Sunday at 1 AM:
0 1 * * 0
Health check ping: Every minute (use sparingly):
* * * * *
Running every minute is fine for lightweight health checks, but avoid expensive operations at this frequency. If your job takes longer than 60 seconds, add a lock file to prevent overlapping runs:
#!/bin/bash
flock -n /tmp/myjob.lock -c 'actual_command_here'
Validating Your Cron Expressions
Cron syntax errors are silent—there's no compiler to catch them. A typo like 0 9 * * 8 (invalid day-of-week) might be rejected by some cron daemons or silently ignored by others.
Before deploying a schedule to production, validate it with a cron parser that shows you the next several run times in human-readable format. Seeing "Next runs: Mon Jul 28 at 9:00 AM, Tue Jul 29 at 9:00 AM..." makes it immediately obvious whether your expression does what you intended.
If you're building a schedule from a requirement ("I need this to run every 4 hours on weekdays"), a cron generator can produce the expression for you—then you can verify it with the parser to double-check the logic.
Both tools run entirely in your browser. No data is sent anywhere, which matters when you're encoding schedules that reveal internal infrastructure timing (backup windows, deployment schedules, maintenance periods).
Quick Reference Card
| Schedule | Expression |
|----------|-----------|
| Every minute | * * * * * |
| Every 5 minutes | */5 * * * * |
| Every hour | 0 * * * * |
| Every day at midnight | 0 0 * * * |
| Weekdays at 9 AM | 0 9 * * 1-5 |
| Every Sunday at 2 AM | 0 2 * * 0 |
| 1st of month at noon | 0 12 1 * * |
| Every 6 hours | 0 */6 * * * |
| Twice daily (8 AM, 8 PM) | 0 8,20 * * * |
| Every 15 min, 9-5, weekdays | */15 9-17 * * 1-5 |
Bookmark this page, or better yet, keep a cron parser tab open whenever you're writing schedules. The five minutes you spend verifying an expression now saves the 3 AM page when a backup doesn't run.
相关文章
Online Word Counter: Count Words, Characters, Lines Instantly
Learn how to use an online word counter to count words, characters, lines, and paragraphs. Understand Chinese vs English counting differences and use cases in writing and development.
UUID Generator: Complete Guide to Unique Identifiers (UUID, NanoID, ULID)
Understand UUID v4, NanoID, and ULID fundamentals. Learn how to use an online UUID generator, and discover best practices for databases, distributed systems, and API tracking.
Online User-Agent Parser: Identify Browser and Device Information
Learn how to parse User-Agent strings. Understand browser, operating system, and device type identification methods, and applications in data analytics and compatibility testing.