Cron’s five fields are easy to read and easy to get wrong. Here is how each field works, a table of schedules you can copy, the traps that make jobs run far too often or never, and what changes on GitHub Actions, Kubernetes and AWS.
Cron is the scheduler built into Linux and macOS, and its five-field syntax is now used almost everywhere jobs run on a timer: CI pipelines, Kubernetes, cloud functions and countless libraries. A single line such as 30 2 * * * says “run at 02:30 every day”.
The syntax is short, which is why mistakes are easy to miss. A job meant to run hourly runs every minute; a job meant for the first Monday runs every day of the first week. This guide explains the fields, gives a table of tested examples, and covers the traps.
Check any expression before you deploy it: paste it into our Cron Expression Tester. It describes the schedule in plain English and lists the next run times in your time zone and in UTC, so a schedule that runs too often is obvious straight away.
The five fields
A cron expression is five fields separated by spaces, always in this order:
┌───────── 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 both Sunday)
│ │ │ │ │
* * * * * command to run
A job runs when the current time matches every field (with one exception for the two day fields, explained below). Hours use the 24-hour clock, so 2 pm is 14.
The special characters
| Symbol | Meaning | Example |
|---|---|---|
* | Every value | * in the hour field: every hour |
, | A list | 6,18: at 6 and at 18 |
- | A range | 1-5 in day of week: Monday to Friday |
/ | A step | */15 in minutes: every 15 minutes. 10-50/20: at 10, 30 and 50 |
Many crontabs also accept shortcuts: @hourly (0 * * * *), @daily (0 0 * * *), @weekly (0 0 * * 0), @monthly (0 0 1 * *), @yearly (0 0 1 1 *) and @reboot, which runs once when the machine starts.
Practical examples
Each of these was checked in the Cron Expression Tester.
| Expression | Runs |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes (288 times a day) |
*/15 * * * * | Every 15 minutes: :00, :15, :30, :45 |
0 * * * * | Every hour, on the hour |
0 */6 * * * | Every 6 hours: 00:00, 06:00, 12:00, 18:00 |
30 2 * * * | Every day at 02:30 |
0 6,18 * * * | Every day at 06:00 and 18:00 |
0 9 * * 1-5 | Weekdays at 09:00 |
*/10 9-17 * * 1-5 | Every 10 minutes from 09:00 to 17:50 on weekdays |
0 22 * * 1-5 | Weekdays at 22:00 |
0 8 * * MON | Every Monday at 08:00 |
0 0 * * 0 | Every Sunday at midnight |
0 10 * * 6,0 | Saturdays and Sundays at 10:00 |
0 0 1 * * | Midnight on the 1st of every month |
0 0 1,15 * * | Midnight on the 1st and 15th |
15 14 1 * * | 14:15 on the 1st of every month |
0 0 1 */3 * | Midnight on the 1st of January, April, July and October |
0 0 1 1 * | Midnight on 1 January |
0 12 25 12 * | Noon on 25 December |
0 3 * * SUN | Sundays at 03:00, a common slot for weekly maintenance |
The traps
1. A step in the wrong field
*/5 means “every fifth value of this field“. In the minute field that is every 5 minutes. In the hour field, 0 */5 * * *, it is every 5 hours, and because the count restarts at midnight it runs at 00:00, 05:00, 10:00, 15:00 and 20:00, leaving a 4-hour gap before the next 00:00.
2. A star left in the minute field
* 9 * * * does not mean “at 9 o’clock”. It means every minute from 09:00 to 09:59: sixty runs. To run once at 09:00, write 0 9 * * *. Likewise 0 * 9 * * runs every hour on the 9th of the month, all 24 times.
3. Day of month and day of week together
This is the trap that catches experienced people. Every other pair of fields must both match. But when both the day-of-month and day-of-week fields are restricted, cron runs the job when either matches.
So 0 9 1-7 * 1, which looks like “09:00 on the first Monday of the month”, actually runs at 09:00 on each of days 1 to 7 and on every Monday. The tester shows this as “on day 1, 2, 3, 4, 5, 6 and 7 of the month or on Monday”.
The usual fix is to schedule by date and check the weekday in the command:
0 9 1-7 * * [ "$(date +\%u)" = "1" ] && /path/to/job.sh
4. The last day of the month
Standard cron has no “last day” value. 0 0 31 * * only runs in the seven months that have 31 days. A common workaround runs on days 28 to 31 and checks whether tomorrow is the 1st (this uses GNU date, as found on most Linux systems):
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/job.sh
5. The % sign
Inside a crontab, an unescaped % is treated as a newline, and everything after it is passed to the command as input. That silently breaks commands such as date +%Y-%m-%d. Escape each one as \%, as in the examples above, or move the command into a script.
6. Sunday is 0 and 7
Both mean Sunday in standard cron, and 1-5 is Monday to Friday. Some other schedulers (Quartz, AWS) number days 1 to 7 starting from Sunday, so 1 means Sunday there, not Monday.
Time zones and daylight saving
Cron uses the time zone of the machine or service it runs on. On servers that is very often UTC, so 0 9 * * * runs at 09:00 UTC, which might be 05:00 in New York or 14:30 in Delhi. The Cron Expression Tester shows every run in your local time and in UTC, and the Time Zone Converter helps you translate a target time into the server’s zone.
Daylight saving time causes two problems on machines that use local time. On the night the clocks go forward, times between about 01:00 and 03:00 may not exist. On the night they go back, an hour happens twice. Implementations deal with this differently: some run skipped jobs straight after the change, some skip them, some run repeated ones twice. To avoid surprises:
- Run servers on UTC, which has no daylight saving.
- Or avoid scheduling important local-time jobs between 01:00 and 03:00.
- Where the scheduler supports it, set the time zone explicitly (for example
CRON_TZin some crontabs, ortimeZonein a Kubernetes CronJob).
How cron differs between platforms
| Platform | Format | Worth knowing |
|---|---|---|
| Linux / macOS crontab | 5 fields | Edit with crontab -e; uses the system time zone |
| GitHub Actions | 5 fields | Always UTC. The shortest interval is 5 minutes, and scheduled runs can start late when GitHub is busy. |
| Kubernetes CronJob | 5 fields | Optional timeZone field; otherwise the controller’s time zone, usually UTC |
| Quartz, Spring, node-cron | 6 fields, seconds first | 0 30 2 * * * is 02:30:00. Quartz also has L, W, # and ?. |
| AWS EventBridge | 6 fields, year last | Written as cron(0 12 * * ? *). One of the two day fields must be ?; days of the week run 1–7 from Sunday. |
The tester accepts the standard five-field form and the six-field form with seconds first, and tells you which it detected. It rejects Quartz-only characters like L and # rather than guessing what they mean.
Making cron jobs reliable
- Use absolute paths. Cron’s
PATHis minimal, sopythonornodemay not be found. Write/usr/bin/python3 /home/me/job.py. - Log the output. By default cron emails output to the user or throws it away. Append
>> /var/log/myjob.log 2>&1so failures leave a trace. - Prevent overlaps. If a job can take longer than its interval, two copies can run at once. On Linux,
flock -n /tmp/myjob.lock /path/to/job.shskips a run while the previous one is still going. - Test the command by hand first, then check the next run times in the tester before saving the crontab.
Frequently asked questions
How do I run a cron job every 5 minutes?
Use */5 * * * *. The */5 is in the minute field, so it runs at minutes 0, 5, 10 and so on, every hour of every day: 288 times a day.
How do I run a cron job at a specific time every day?
Put the minute first and the hour second. 30 2 * * * runs at 02:30 every day; 0 18 * * * runs at 18:00 (6 pm).
Can cron run a job every 30 seconds?
Standard cron cannot; its smallest unit is one minute. Schedulers that use a six-field format with seconds, such as Quartz, Spring and node-cron, can. With plain cron, a common workaround is two entries for the same minute, one of them starting with sleep 30.
Why does my cron job work when I run it by hand but not from cron?
Cron runs jobs with a minimal environment: a short PATH, no shell profile and a different working directory. Use absolute paths for commands and files, set any environment variables in the crontab or the script, and redirect output to a log file so you can see the error.