What Is a Cron Expression? Syntax, Fields and 15 Examples
A cron expression is a short string that tells a scheduler when to run a job. The standard form has five fields separated by spaces: minute, hour, day of month, month and day of week. For example, */15 * * * * means "every 15 minutes": */15 in the minute field matches minutes 0, 15, 30 and 45, and the four asterisks mean any hour, any day, any month and any weekday.
Try it free: Crontab Generator & Cron Job Scheduler - Create Cron Expressions Free to use, no account needed.
The name comes from cron, the Unix job scheduler, but the same syntax now drives CI pipelines, cloud schedulers and application frameworks, sometimes with extra fields. This guide covers the syntax, 15 verified examples, the common variants and the mistakes that make a job run at the wrong time. To check any expression as you read, paste it into the free Crontab Generator, which describes the schedule in plain English.
The five fields of a cron expression
The fields are read from left to right:
| Position | Field | Allowed values | Names |
|---|---|---|---|
| 1 | Minute | 0–59 | – |
| 2 | Hour | 0–23 | – |
| 3 | Day of month | 1–31 | – |
| 4 | Month | 1–12 | jan–dec |
| 5 | Day of week | 0–7 (0 and 7 are both Sunday) | sun–sat |
Hours use the 24-hour clock, so 2 PM is 14. Names are the first three letters of the English month or day, and case doesn't matter. Some implementations, including the cron that ships with macOS, don't accept names inside ranges or lists, so 1-5 is more portable than mon-fri.
In a crontab file, the command follows the five fields: 0 2 * * * /home/me/backup.sh. The system-wide /etc/crontab on Linux also has a user name field before the command.
Special characters: asterisk, comma, hyphen and slash
Four characters do almost all the work:
| Character | Meaning | Example | Matches |
|---|---|---|---|
* |
Every value | * in the hour field |
Hours 0, 1, 2 … 23 |
, |
List | 8,20 in the hour field |
Hours 8 and 20 |
- |
Range (inclusive) | 1-5 in the day-of-week field |
Monday to Friday |
/ |
Step | */15 in the minute field |
Minutes 0, 15, 30, 45 |
You can combine them. 9-17/2 in the hour field matches 9, 11, 13, 15 and 17, and 0-4,8-12 is a list of two ranges.
Steps restart in every cycle. */7 in the minute field matches 0, 7, 14 … 56, and then 0 again at the start of the next hour, so the gap between :56 and :00 is four minutes, not seven. A step that doesn't divide 60 minutes or 24 hours evenly never gives a perfectly regular interval.
15 cron expression examples
Every example was checked field by field. Times are in the scheduler's time zone.
| Cron expression | When it runs |
|---|---|
* * * * * |
Every minute |
*/5 * * * * |
Every 5 minutes (:00, :05, :10 …) |
30 * * * * |
Every hour at half past (00:30, 01:30 …) |
0 */2 * * * |
Every 2 hours on the hour (00:00, 02:00 … 22:00) |
0 2 * * * |
Every day at 02:00 |
0 8,20 * * * |
Twice a day, at 08:00 and 20:00 |
0 9 * * 1-5 |
Monday to Friday at 09:00 |
*/15 9-17 * * 1-5 |
Every 15 minutes from 09:00 to 17:45, Monday to Friday |
0 6 * * 0,6 |
Saturdays and Sundays at 06:00 |
0 0 * * 0 |
Every Sunday at midnight |
0 22 * * 5 |
Every Friday at 22:00 |
0 0 1 * * |
The 1st of every month at midnight |
0 12 15 * * |
The 15th of every month at 12:00 |
0 0 1 1,4,7,10 * |
Quarterly: 1 January, April, July and October at midnight |
0 0 1 1 * |
Once a year, 1 January at midnight |
Two details are easy to miss. First, ranges include both ends, so 9-17 includes hour 17: */15 9-17 * * 1-5 runs 36 times a day and the last run is at 17:45, not 17:00. Second, 0 0 1 */3 * gives the same quarterly schedule, because */3 in the month field starts at 1 and matches months 1, 4, 7 and 10.
Nonstandard macros: @daily, @weekly and @reboot
Many cron implementations accept a keyword in place of the five fields:
| Macro | Same as | Runs |
|---|---|---|
@yearly or @annually |
0 0 1 1 * |
Midnight on 1 January |
@monthly |
0 0 1 * * |
Midnight on the 1st of each month |
@weekly |
0 0 * * 0 |
Midnight on Sunday |
@daily or @midnight |
0 0 * * * |
Midnight every day |
@hourly |
0 * * * * |
Minute 0 of every hour |
@reboot |
– | Once, when the cron daemon starts |
Vixie cron, cronie and the cron on macOS support all of them, and Kubernetes CronJobs accept the time-based ones but not @reboot. They are not part of the POSIX standard, so check the documentation before using them elsewhere.
Cron variants: seconds, years and extra characters
Not every scheduler reads five fields, so check which dialect you are writing for.
- Quartz (Java) uses six or seven fields: seconds, minutes, hours, day of month, month, day of week and an optional year. One of the two day fields must be
?("no specific value"). Quartz also supportsL(last),W(nearest weekday) and#(nth weekday of the month):0 15 10 L * ?runs at 10:15 on the last day of every month, and0 15 10 ? * 6#3runs at 10:15 on the third Friday, because Quartz numbers the weekdays 1–7 starting with Sunday. - Spring
@Scheduled(cron = "…")expressions have six fields: seconds first, then the usual five, with no year. - AWS EventBridge rules use
cron(minutes hours day-of-month month day-of-week year): six fields, no seconds, a year at the end, and a?in one of the two day fields.cron(0 12 * * ? *)runs every day at 12:00 UTC. Rules on an event bus run in UTC, while EventBridge Scheduler lets you choose a time zone. Like Quartz, AWS numbers the weekdays 1–7 with 1 = Sunday. - Kubernetes CronJobs use the standard five fields in
spec.schedule. Withoutspec.timeZone, the schedule follows the time zone of the kube-controller-manager. Set it to an IANA name such asEurope/Berlinto pin it. - GitHub Actions
scheduletriggers use five fields and run in UTC unless you set an optional IANA time zone. The shortest supported interval is once every five minutes, the@daily-style macros are not supported, and scheduled runs can start late when GitHub is busy, especially at the top of the hour.
The day-of-month and day-of-week trap
In standard (Vixie) cron, the two day fields are combined with OR, not AND, when both are restricted. If either one is *, only the other counts. So 0 9 1-7 * 1 doesn't mean "the first Monday of the month". It runs at 09:00 on each of days 1 to 7 and on every Monday, which adds up to 10 or 11 runs a month.
To run only on the first Monday, keep one day field in the expression and check the other in the command:
0 9 1-7 * * [ "$(date +\%u)" = 1 ] && /path/to/report.sh
date +%u prints the weekday as a number from 1 to 7, with Monday = 1. The percent sign has to be escaped as \% because cron turns an unescaped % in the command into a newline. Quartz and AWS avoid the ambiguity by requiring ? in one of the two day fields.
Time zones and daylight saving time
Cron runs in the time zone of the machine or service that evaluates it, which on servers and cloud platforms is often UTC, not your local time. 0 9 * * * on a UTC server runs at 09:00 UTC, which is 05:00 in New York during daylight saving time (UTC−4). The time zone converter helps you translate a local time into the scheduler's zone.
Daylight saving time adds two edge cases in zones that use it: when the clocks go forward, one local hour doesn't exist, and when they go back, one hour happens twice. Debian's cron and cronie handle small clock changes for jobs set to a fixed time: a run that falls in the skipped hour happens soon after the change, and a run in the repeated hour is not repeated. Other schedulers behave differently, so the safest choices are to run servers in UTC or keep important jobs out of the hours when clocks change, typically between 01:00 and 03:00 local time.
How to test a cron expression
A wrong cron expression rarely produces an error; the job just runs at the wrong time, or never. A short routine catches most problems:
- Read it back in plain words. Paste the expression into a describer and check that the sentence matches what you meant, especially the two day fields.
- Test the command with a short schedule. Use
* * * * *temporarily so the job runs within a minute, then switch to the real schedule. - Capture the output. Add
>> /tmp/myjob.log 2>&1after the command so errors are written to a file instead of being lost. - Check the cron log. Use
grep CRON /var/log/syslogorjournalctl -u cronon Debian and Ubuntu, andjournalctl -u crondon Fedora and RHEL. - Use absolute paths. Cron starts jobs with a minimal environment and a short
PATH, so a command that works in your terminal can fail under cron.
Build one with the Crontab Generator
The free Crontab Generator runs entirely in your browser. Type an expression or click a quick preset such as Every 15 min, Daily 2am or Every Monday, and the tool:
- validates the syntax and tells you when an expression is invalid;
- describes the schedule in plain English (in German on the German version of the page);
- accepts the standard five fields, an optional seconds field at the start, month and weekday names, and 7 for Sunday;
- expands macros such as
@weeklyinto their five-field equivalent and explains what@rebootdoes; - shows a cheat sheet with every symbol and an example.
Switches set a more verbose description, 24-hour times, and whether weekday numbers start at 0 for Sunday. Quartz-only characters (L, W, #) and a year field are not accepted. The tool only builds and checks the expression; to schedule the job, copy it into crontab -e or your platform's configuration.
FAQ
What is the difference between cron and crontab?
Cron is the background service (daemon) that runs scheduled jobs. A crontab, short for "cron table", is the file that lists them, one job per line: a cron expression followed by a command. You edit your own crontab with crontab -e and list it with crontab -l.
Can a cron job run every 30 seconds?
Not in standard cron, whose smallest unit is one minute. Schedulers with a seconds field can: Spring accepts */30 * * * * *, and Quartz needs a ? in one day field, as in */30 * * * * ?. With classic cron, a common workaround is two lines that both run every minute, one of them starting with sleep 30; before the command.
How do I run a cron job on the last day of the month?
Standard cron has no "last day" symbol, but Quartz and AWS support L in the day-of-month field. In classic cron, schedule days 28 to 31 and let the command check whether tomorrow is the 1st:
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/job.sh
date -d tomorrow is GNU date syntax, as found on Linux. On macOS, use date -v+1d instead.
Try it free: Crontab Generator & Cron Job Scheduler - Create Cron Expressions Free to use, no account needed.