Cron Syntax Explained

A cron expression is five fields separated by spaces: minute, hour, day of month, month and day of week. Each field holds a number, an asterisk meaning “every”, or a combination built with commas, hyphens and slashes. So 30 2 * * 1 means 02:30 every Monday, and */15 * * * * means every 15 minutes. The same syntax is used by crontab on Linux and macOS, Kubernetes CronJobs, GitHub Actions and most hosting schedulers.

The five 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; 0 and 7 are Sunday)
│ │ │ │ │
* * * * *  command to run

Hours use the 24-hour clock, so 6 p.m. is 18. Month and weekday names are three-letter English abbreviations and are not case sensitive. Names cannot be used in ranges on every implementation, so 1-5 is more portable than MON-FRI.

The four special characters

CharacterMeaningExampleResult
*Every value0 * * * *Every hour on the hour
,A list0 9,13,17 * * *At 09:00, 13:00 and 17:00
-A range0 9-17 * * 1-5Hourly from 09:00 to 17:00 on weekdays
/A step*/10 * * * *Every 10 minutes

Steps count from the start of the range, not from when the job was installed. */10 fires at :00, :10, :20 and so on. A step that does not divide evenly restarts each hour or day: */25 in the minute field fires at :00, :25 and :50, then :00 again ten minutes later. To run something every 90 minutes you need two lines, because no single expression produces that rhythm.

Not sure what an expression does? Paste it into the Cron Expression Parser to get a plain-English reading and the next run times.

Shortcuts

ShortcutEquivalentMeaning
@hourly0 * * * *Start of every hour
@daily / @midnight0 0 * * *Midnight every day
@weekly0 0 * * 0Midnight on Sunday
@monthly0 0 1 * *Midnight on the 1st
@yearly / @annually0 0 1 1 *Midnight on 1 January
@rebootNo equivalentOnce, when the cron daemon starts

The day-of-month and day-of-week trap

When both day fields are restricted, standard cron runs the job if either matches. 0 0 13 * 5 does not mean “Friday the 13th”; it means every 13th of the month and every Friday. To get the intersection, schedule on one field and test the other inside the command, for example running every Friday and exiting unless date +%d is 13. If either field is an asterisk, the rule does not apply.

Why a job works in the terminal but not in cron

  1. A minimal environment. Cron does not load your shell profile. PATH is often just /usr/bin:/bin, so commands installed elsewhere are not found. Use absolute paths, or set PATH= at the top of the crontab.
  2. Unescaped percent signs. In a crontab line, % is turned into a newline. date +%Y-%m-%d must be written date +\%Y-\%m-\%d.
  3. No execute permission. A script called directly needs its execute bit set, usually with chmod 755. The Chmod Calculator shows what a mode allows.
  4. Lost output. Errors are mailed to the local user, where nobody reads them. Redirect both streams to a log: /path/to/job.sh >> /var/log/job.log 2>&1.
  5. Relative paths. Cron starts jobs in the user's home directory, not the script's folder. Use cd /path && first or absolute file paths.

Time zones and daylight saving

A system crontab runs in the server's local time zone. Many hosted schedulers use UTC: GitHub Actions schedules always do, and Kubernetes CronJobs use the controller's zone unless you set the timeZone field. Daylight saving causes the remaining surprises. A job at 02:30 may be skipped on the night clocks spring forward and may run twice, or once, on the night they fall back, depending on the implementation. Run servers on UTC, or keep important jobs out of the 01:00 to 03:00 window. If you are checking log timestamps after the fact, the Unix Timestamp Converter turns epoch values into readable dates.

Cron dialects that look similar

SystemFieldsDifferences
crontab (Vixie cron, cronie)5The reference syntax; system crontabs add a user column
Kubernetes CronJob5Standard syntax, optional timeZone field
GitHub Actions5Always UTC; shortest interval is 5 minutes; runs can be delayed at busy times
AWS EventBridge6Adds a year field; one day field must be ?
Quartz, Spring6 or 7Seconds come first; adds ?, L, W and #

Copying an expression between these systems is the most common source of “it ran at the wrong time” bugs. A five-field expression pasted into Quartz shifts every field one place to the left.

Good habits

  • Add a comment above each line saying what it does and who owns it.
  • Avoid stampedes: if many machines run the same job, pick an odd minute such as 17 rather than 0.
  • Prevent overlaps on long jobs with a lock, for example flock -n /tmp/job.lock /path/to/job.sh.
  • Keep crontabs in version control, and test new expressions before deploying them.

Sources and further reading