Core answer: A cron expression has five fields: minute hour day month weekday, space-separated. Daily at 3 AM: 0 3 * * *; every 5 minutes: */5 * * * *; weekdays at 9:30: 30 9 * * 1-5. Memorize the order "minute-hour-day-month-weekday" — 90% of mistakes are swapped fields.
The five fields
```
┌ minute (0–59)
│ ┌ hour (0–23)
│ │ ┌ day of month (1–31)
│ │ │ ┌ month (1–12)
│ │ │ │ ┌ weekday (0–6; both 0 and 7 are Sunday)
* * * * *
```
Four special symbols
| Symbol | Meaning | Example |
|---|---|---|
| * | any value | * * * * * = every minute |
| */n | every n steps | */10 * * * * = every 10 minutes |
| a-b | range | 0 9-18 * * * = on the hour from 9 to 18 |
| a,b,c | list | 0 9,12,18 * * * = three specific times |
Combined: */15 9-18 * * 1-5 = every 15 minutes, 9:00–18:00, weekdays.
20 common expressions
| Need | Expression |
|---|---|
| Every minute | * * * * * |
| Every 5 minutes | */5 * * * * |
| Top of every hour | 0 * * * * |
| Daily at midnight | 0 0 * * * |
| Daily at 3 AM | 0 3 * * * |
| Daily at 9:30 | 30 9 * * * |
| Minutes 15 and 45 of each hour | 15,45 * * * * |
| Weekdays at 9 AM | 0 9 * * 1-5 |
| Weekends at 10 AM | 0 10 * * 6,0 |
| Mondays at 8 AM | 0 8 * * 1 |
| 1st of month at midnight | 0 0 1 * * |
| Last day of month (workaround) | 0 0 28-31 * * (see pitfalls) |
| First day of each quarter, 1 AM | 0 1 1 1,4,7,10 * |
| January 1st | 0 0 1 1 * |
| Every 10 min, 9–18, weekdays | */10 9-18 * * 1-5 |
| Every 2 hours | 0 */2 * * * |
| Daily at 6:00 and 18:00 | 0 6,18 * * * |
| Sun–Thu at 11 PM | 0 23 * * 0-4 |
| 15th of month at noon | 0 12 15 * * |
| Once at boot | @reboot (non-standard extension) |
Three real-world examples
Example 1 (database backup): daily at 2:30 AM, off-peak → 30 2 * * * /scripts/backup.sh
Example 2 (log cleanup): Sundays at 4 AM, delete logs older than 7 days → 0 4 * * 0 find /logs -mtime +7 -delete
Example 3 (API health check): every 10 minutes during work hours, silent otherwise → */10 8-19 * * 1-5 curl -s https://api/health
crontab essentials
crontab -eto edit,crontab -lto list,crontab -rto wipe (dangerous)- Use absolute paths: cron's PATH is minimal (/usr/bin:/bin); cd inside your script
- Redirect output:
>> /var/log/job.log 2>&1, or errors vanish - Declare environment variables at the top of the crontab file
- Changes take effect immediately — no service restart needed
Six frequent pitfalls
- Swapped fields: writing
3 0 * * *for "3 AM" (that is actually midnight on the 3rd of each month) — correct:0 3 * * * - Percent escaping: % inside the command must be written as \%, otherwise it becomes a newline
- Relative paths: ./data fails — cron's working directory is not your script's directory
- "Last day of month": cron has no L; use
0 0 28-31 * *plus an in-script guard [ $(date -d tomorrow +\%d) -eq 1 ] - "Every 90 minutes": */90 is invalid (minute field maxes at 59); split into
0 0,3,6,9,12,15,18,21 * * *or use two lines - Time zones: cron runs on the server's system time zone — check with timedatectl on cross-region servers
Common mistakes and myths
- "Day-of-month AND day-of-week combine as AND" — in standard cron, when both are restricted (not *), they combine as OR:
0 9 1 * 1runs on the 1st of each month and every Monday. - "Restart needed after editing" — no; saving applies instantly. If a job doesn't run, check grep CRON /var/log/syslog first.
- "Second-level scheduling" — standard cron's finest granularity is one minute; use systemd timers or an in-process loop for seconds.
- "Weekend is 1-5" — 1-5 is Monday–Friday (workdays); weekends are 6,0 or 6,7.
Use the [Cron Expression Calculator](/c/dev/cron) to preview the next 10 run times instantly, and the [Timestamp Converter](/c/dev/timestamp) for log timestamps.