Overview
Cron expressions describe recurring schedules as five (or six) space-separated fields. The same grammar appears in /etc/crontab, GitHub Actions schedule:, AWS EventBridge, Kubernetes CronJobs, and most task schedulers. This card covers the field grammar, special characters, common patterns, and the edge cases that cause schedules to run at the wrong time or not at all.
Field positions
Standard cron uses five fields. Some systems add a seconds field at position 0 or a year field at position 5.
| Field | Position | Range | Notes |
|---|---|---|---|
| Minute | 1st | 0-59 | 0 = top of the hour |
| Hour | 2nd | 0-23 | 24-hour clock, UTC unless configured otherwise |
| Day of month | 3rd | 1-31 | L for last day (some engines) |
| Month | 4th | 1-12 or JAN-DEC | Month names are case-insensitive |
| Day of week | 5th | 0-7 or SUN-SAT | 0 and 7 both mean Sunday |
| Seconds | 0th (optional) | 0-59 | AWS, Spring, and Quartz add this before minute |
| Year | 6th (optional) | 1970-2099 | AWS EventBridge supports this |
GitHub Actions uses 5-field UTC cron: on: schedule: - cron: "0 9 * * 1" runs at 09:00 UTC every Monday.
Special characters
| Character | Meaning | Example | Matches |
|---|---|---|---|
* | Every value in field | * * * * * | Every minute |
, | List separator | 0 9,17 * * * | 09:00 and 17:00 |
- | Range | 0 9-17 * * * | Every hour 09:00 to 17:00 |
/ | Step | */15 * * * * | Every 15 minutes |
/ with range | Step in range | 0 8-20/2 * * * | Every 2 hours from 08:00 to 20:00 |
L | Last (some engines) | 0 0 L * * | Last day of the month at midnight |
W | Nearest weekday (Quartz) | 0 0 15W * * | Nearest weekday to the 15th |
# | Nth weekday (Quartz) | 0 0 * * 5#2 | Second Friday of the month |
? | No specific value (Quartz) | 0 0 ? * MON | Required when day-of-month and day-of-week both set |
L, W, and # are extensions. Standard cron (vixie-cron, GitHub Actions) does not support them.
Common schedule patterns
| Expression | Meaning |
|---|---|
* * * * * | Every minute |
0 * * * * | Every hour, on the hour |
0 0 * * * | Daily at midnight UTC |
0 9 * * * | Daily at 09:00 UTC |
0 9 * * 1 | Every Monday at 09:00 |
0 9 * * 1-5 | Weekdays at 09:00 |
0 9 * * 6,0 | Weekends at 09:00 |
0 0 1 * * | First day of every month at midnight |
0 0 1 1 * | January 1st at midnight (annual) |
*/5 * * * * | Every 5 minutes |
*/15 9-17 * * 1-5 | Every 15 min during business hours on weekdays |
0 0,12 * * * | Twice daily: midnight and noon |
@reboot | At system startup (vixie-cron only) |
@hourly | Same as 0 * * * * |
@daily | Same as 0 0 * * * |
@weekly | Same as 0 0 * * 0 |
@monthly | Same as 0 0 1 * * |
Use https://crontab.guru to verify expressions interactively.
Timezone handling
Most cron daemons run in the system timezone or UTC. Timezone mismatches are the most common source of “wrong time” bugs.
| System | Timezone default | How to change |
|---|---|---|
vixie-cron (Linux) | System timezone | CRON_TZ=America/New_York line in crontab |
crontab -e | System timezone | CRON_TZ= or TZ= at top of file |
| GitHub Actions | UTC always | Convert local time to UTC before writing expression |
| AWS EventBridge | UTC | Set timezone in the EventBridge rule (separate field) |
| Kubernetes CronJob | UTC | Set spec.timeZone: "America/New_York" (k8s 1.27+) |
A job scheduled for 0 9 * * * on a UTC system runs at 09:00 UTC, which is 04:00 EST in winter and 05:00 EDT in summer.
Validation and testing
| Tool | Purpose |
|---|---|
crontab -l | List current user’s crontab |
crontab -e | Edit current user’s crontab |
/var/log/syslog or journalctl -u cron | View cron execution log |
run-one | Prevent overlapping runs of the same job |
flock -n /tmp/job.lock cmd | Advisory lock to prevent overlap |
crontab.guru | Web expression validator |
Common gotchas
- Day-of-week and day-of-month are OR’d in vixie-cron:
0 0 1 * 1fires on the 1st of the month OR every Monday, not the intersection. Use a script check if you need AND logic. - The
*/nstep always starts at 0 (or 1 for DOM):*/7means 0, 7, 14, 21, 28, 35, 42, 49, 56, not evenly spaced seven-minute slots. - Environment variables in crontabs are minimal.
PATH,HOME, andSHELLare set; your login shell’s~/.bashrcis not sourced. Use absolute paths for binaries. - Output not redirected to a file is mailed to the local user via
sendmail. On servers without a mail daemon, this fills/var/spool/mail. Add>/dev/null 2>&1to suppress. - GitHub Actions
scheduleevents are queued, not guaranteed. During high load, runs can be delayed by minutes or hours. Do not rely on sub-minute precision.