Cron and crontab syntax, explained
Cron syntax is five fields, minute, hour, day of month, month and day of week, that tell a scheduler when to run a job. In the crontab format, each line is those five fields followed by the command. Paste an expression to see what it does, or copy a ready-made schedule below.
The five fields
in order, left to right| # | Field | Allowed values |
|---|---|---|
| 1 | Minute | 0-59 |
| 2 | Hour | 0-23 |
| 3 | Day of month | 1-31 |
| 4 | Month | 1-12 or JAN-DEC |
| 5 | Day of week | 0-6 or SUN-SAT |
Special characters
| Character | Meaning | Example | Runs |
|---|---|---|---|
* | Every value of the field | * * * * * | Every minute |
, | A list of values | 0 9,17 * * * | At 9:00 and 17:00 every day |
- | A range of values | 0 9 * * 1-5 | At 9:00, Monday to Friday |
/ | A step through a range | */15 * * * * | Every 15 minutes (:00, :15, :30, :45) |
Shortcuts such as @daily and @reboot replace all five fields. Run a job at startup with @reboot. To schedule a PHP script, see how to run PHP as a cron job, and to add or change jobs, how to edit crontab.
Day of month and day of week combine with OR
0 0 13 * 5 runs at midnight on the 13th of every month and on every Friday, not only on Friday the 13th.Crontab format: one job per line
A crontab is a plain text file with one job per line: the five schedule fields, then the command to run. Everything after the fifth field, to the end of the line, is the command. Cron also reads comment lines, blank lines and variable settings, so a typical crontab looks like this:
# Variables apply to the jobs below them
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=ops@example.com
# m h dom mon dow command
*/5 * * * * /usr/local/bin/check-queue
30 2 * * 0 /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
@reboot /usr/local/bin/start-worker| Line | What cron does with it |
|---|---|
# comment | Ignored. The # has to start the line; after a command it is passed to the shell |
NAME=value | Sets an environment variable for the jobs below it |
m h dom mon dow command | A job: five schedule fields, then the command, separated by spaces or tabs |
@daily command | A shortcut such as @hourly, @daily or @reboot in place of the five fields |
m h dom mon dow user command | A job in /etc/crontab or /etc/cron.d/, which names the user to run as |
| Variable | Meaning |
|---|---|
SHELL | The shell that runs each command. Default /bin/sh |
PATH | Where commands are looked up. Default /usr/bin:/bin, so use full paths or set it |
MAILTO | Who gets each job's output by email. Default the crontab's owner; MAILTO="" sends none |
HOME | The working directory for commands. Default the owner's home directory |
The last line needs a line break after it, or crontab refuses the file with “new crontab file is missing newline before EOF”. To install or change a crontab, see how to edit crontab.
Days of the week in cron
The fifth field is the day of the week. Cron counts from Sunday as 0, so Monday is 1 and Saturday is 6, and most crons also accept 7 for Sunday. Three-letter names work too, in any case: MON-FRI is the same as 1-5.
| Day | Number | Name | AWS and Quartz |
|---|---|---|---|
| Sunday | 0 or 7 | SUN | 1 |
| Monday | 1 | MON | 2 |
| Tuesday | 2 | TUE | 3 |
| Wednesday | 3 | WED | 4 |
| Thursday | 4 | THU | 5 |
| Friday | 5 | FRI | 6 |
| Saturday | 6 | SAT | 7 |
| Expression | Runs |
|---|---|
| 0 9 * * 1-5 | At 09:00, Monday through Friday (weekdays) |
| 0 10 * * 0,6 | At 10:00 on Saturday and Sunday (weekends) |
| 0 9 * * 1,3,5 | At 09:00 on Monday, Wednesday and Friday |
| 30 8 * * 1 | At 08:30 every Monday |
| 0 0 * * 0 | At midnight every Sunday |
GitHub Actions, Kubernetes and Vercel document the day of week as 0-6, so write Sunday as 0 there, and Vercel doesn't accept names. AWS EventBridge and Quartz number the days 1-7 starting on Sunday, so weekdays are 2-6 or MON-FRI, and the other day field has to be ?.
Run a cron job on a specific date
A date goes in the third and fourth fields: the day of the month (1-31), then the month (1-12, or JAN to DEC). So 0 9 25 12 * runs at 09:00 on 25 December. Leave the day of the week as *.
| Expression | Runs |
|---|---|
| 0 9 25 12 * | At 09:00 on the 25th of December, every year |
| 0 9 25 DEC * | The same: months take three-letter names |
| 30 6 15 * * | At 06:30 on the 15th of every month |
| 0 0 1,15 * * | At 00:00 on the 1st and 15th of every month |
| 0 0 1 1,4,7,10 * | At 00:00 on the 1st of January, April, July and October |
| 0 0 29 2 * | At 00:00 on 29 February, so only in leap years |
Cron has no year field, so a date repeats every year. For a job that runs once, use at instead: echo /usr/local/bin/send-report | at 09:00 2026-12-25. A date that never comes, such as 0 0 31 2 *, is accepted but never runs, and 0 0 31 * * skips every month with 30 days or fewer.
Filling in both day fields doesn't narrow the date: cron runs when either one matches, so 0 9 13 * 5 runs on every 13th and on every Friday. For Friday the 13th, or for the last day of the month, which no field can express, schedule the possible days and let date decide in the command. Its % signs need a backslash, as the next section explains.
# Friday the 13th: run on every 13th, then test the weekday
0 9 13 * * [ "$(date +\%u)" = 5 ] && /usr/local/bin/friday-13th
# Last day of the month: run on the 28th-31st, then test tomorrow
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /usr/local/bin/month-enddate -d tomorrow is GNU date, as on Linux. In AWS EventBridge and Quartz, L in the day-of-month field means the last day, and 6#2 in the day of week means its second Friday.
Dates in a cron command: escape %
Cron treats % in the command part of a crontab line as a newline, and sends everything after the first one to the command as input. So date +%F, which works in a terminal, breaks in crontab: write each % as \%. Cron removes the backslash before it runs the command. The five schedule fields never contain %, so this only affects the command.
# Back up /var/www to a file named by date, such as site-2026-10-13.tgz
0 2 * * * tar -czf /backups/site-$(date +\%F).tgz /var/www| In crontab | Prints |
|---|---|
date +\%F | 2026-10-13 |
date +\%Y\%m\%d | 20261013 |
date +\%Y-\%m-\%d_\%H\%M | 2026-10-13_0200 |
date +\%s | 1791856800 (seconds since 1970, UTC) |
The date comes from the time zone cron runs in, usually the server's. To check a line before it runs, see how to edit crontab.
Cron cheat sheet: common schedules
Copy the expression for the schedule you need. Each card opens a page with the expression explained and the syntax for crontab, GitHub Actions, Kubernetes, AWS and Vercel.
- Every minute
* * * * * - Every 5 minutes
*/5 * * * * - Every 10 minutes
*/10 * * * * - Every 15 minutes
*/15 * * * * - Every 30 minutes
*/30 * * * * - Every hour
0 * * * * - Every 2 hours
0 */2 * * * - Every day at midnight
0 0 * * * - Weekdays at 9:00
0 9 * * 1-5 - Every Monday
0 0 * * 1 - Every Sunday
0 0 * * 0 - First of the month
0 0 1 * *
| Expression | Runs |
|---|---|
| */20 * * * * | Every 20 minutes |
| 0 */6 * * * | Every 6 hours, at 00:00, 06:00, 12:00 and 18:00 |
| 0 9,17 * * * | At 09:00 and 17:00 every day |
| 0 12 * * 1-5 | At 12:00, Monday through Friday |
| 0 9-17 * * 1-5 | Every hour from 09:00 through 17:00, Monday through Friday |
| 0 0 * * 0,6 | At midnight on Saturday and Sunday |
| 0 0 1 */3 * | At midnight on the 1st of January, April, July and October |
| 0 0 1 1 * | At midnight on 1 January |
| Shortcut | Same as | Runs |
|---|---|---|
@hourly | 0 * * * * | At the start of every hour |
@daily | 0 0 * * * | Every day at midnight (also @midnight) |
@weekly | 0 0 * * 0 | Every Sunday at midnight |
@monthly | 0 0 1 * * | At midnight on the 1st of every month |
@yearly | 0 0 1 1 * | At midnight on 1 January (also @annually) |
@reboot | – | Once, when cron starts after a boot |
Shortcuts work in Linux crontab and Kubernetes CronJobs. GitHub Actions, AWS EventBridge and Vercel take only the five fields, and Jenkins reads @daily as once a day at a time it picks per job. For @reboot, see how to run a cron job at startup, and to check any other expression, paste it into the cron expression validator.
Platform guides
- JenkinsH spreads jobs across the hour, TZ= sets the zone, and both day fields must match.
- GitHub ActionsUTC unless you set timezone, every 5 minutes at most, and runs often start late.
- VercelSet in vercel.json, always UTC, once a day on Hobby, and failed runs aren't retried.
- QuartzSeconds first, one day field set to ?, Sunday is 1, and L, W and # for special days.