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

SymbolMeaningExample
*any value* * * * * = every minute
*/nevery n steps*/10 * * * * = every 10 minutes
a-brange0 9-18 * * * = on the hour from 9 to 18
a,b,clist0 9,12,18 * * * = three specific times

Combined: */15 9-18 * * 1-5 = every 15 minutes, 9:00–18:00, weekdays.

20 common expressions

NeedExpression
Every minute* * * * *
Every 5 minutes*/5 * * * *
Top of every hour0 * * * *
Daily at midnight0 0 * * *
Daily at 3 AM0 3 * * *
Daily at 9:3030 9 * * *
Minutes 15 and 45 of each hour15,45 * * * *
Weekdays at 9 AM0 9 * * 1-5
Weekends at 10 AM0 10 * * 6,0
Mondays at 8 AM0 8 * * 1
1st of month at midnight0 0 1 * *
Last day of month (workaround)0 0 28-31 * * (see pitfalls)
First day of each quarter, 1 AM0 1 1 1,4,7,10 *
January 1st0 0 1 1 *
Every 10 min, 9–18, weekdays*/10 9-18 * * 1-5
Every 2 hours0 */2 * * *
Daily at 6:00 and 18:000 6,18 * * *
Sun–Thu at 11 PM0 23 * * 0-4
15th of month at noon0 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 -e to edit, crontab -l to list, crontab -r to 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

  1. Swapped fields: writing 3 0 * * * for "3 AM" (that is actually midnight on the 3rd of each month) — correct: 0 3 * * *
  2. Percent escaping: % inside the command must be written as \%, otherwise it becomes a newline
  3. Relative paths: ./data fails — cron's working directory is not your script's directory
  4. "Last day of month": cron has no L; use 0 0 28-31 * * plus an in-script guard [ $(date -d tomorrow +\%d) -eq 1 ]
  5. "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
  6. 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 * 1 runs 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.