Cron expressions explained, with examples
9 min read · Updated 4 October 2026
Cron is the standard way to run something on a schedule on Linux and Unix systems: backups every night, a report every Monday, a cleanup script every fifteen minutes. The same syntax has spread far beyond Unix, into CI pipelines, cloud schedulers, container platforms and application frameworks. So it is worth learning once, properly.
A cron schedule is written as an expression of five fields. This guide explains each field, the four special characters, a set of everyday examples, and the handful of traps that cause jobs to run at the wrong time, or not at all.
The five fields
A standard cron expression has five fields separated by spaces:
┌───────────── minute (0-59)
│ ┌─────────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌─────── month (1-12 or JAN-DEC)
│ │ │ │ ┌───── day of week (0-6 or SUN-SAT; 0 = Sunday, 7 is also Sunday on most systems)
│ │ │ │ │
30 2 * * *
This example means "at minute 30 of hour 2, every day of the month, every month, every day of the week". In other words, 2:30 a.m. every day.
Hours use the 24-hour clock, so 2 p.m. is 14. In a crontab file, the five fields are followed by the command to run:
30 2 * * * /usr/local/bin/backup.sh
The special characters
| Character | Meaning | Example | Result |
|---|---|---|---|
* |
Every value | * in the hour field |
Every hour |
, |
A list | 1,15 in day of month |
The 1st and the 15th |
- |
A range | 1-5 in day of week |
Monday to Friday |
/ |
A step | */15 in minutes |
Minutes 0, 15, 30 and 45 |
Steps can be combined with ranges: 8-17/2 in the hour field means every second hour from 8 to 17, which is 8, 10, 12, 14 and 16. A step can also start from a specific value: 5/15 in the minute field means 5, 20, 35 and 50.
Many cron implementations also accept month and day names (JAN, MON), which make expressions easier to read: 0 9 * * MON-FRI.
Everyday examples
| Expression | Meaning |
|---|---|
*/5 * * * * |
Every 5 minutes |
0 * * * * |
Every hour, on the hour |
15 * * * * |
Every hour at quarter past |
30 2 * * * |
Every day at 02:30 |
0 9 * * 1-5 |
Weekdays at 09:00 |
0 18 * * 5 |
Fridays at 18:00 |
0 3 * * 0 |
Sundays at 03:00 |
0 0 1 * * |
Midnight on the 1st of every month |
15 10 1,15 * * |
10:15 on the 1st and 15th of each month |
0 8-17/2 * * 1-5 |
Weekdays at 08:00, 10:00, 12:00, 14:00 and 16:00 |
0 0 1 1 * |
Midnight on 1 January |
0 6 * 3-10 * |
06:00 every day from March to October |
The cron expression builder works in both directions: build an expression from a form, or paste one and read it in plain English, with a list of the next run times so you can confirm it does what you expect.
Shortcuts
Most cron versions accept a few named shortcuts in place of the five fields:
| Shortcut | Equivalent | Meaning |
|---|---|---|
@hourly |
0 * * * * |
Start of every hour |
@daily (or @midnight) |
0 0 * * * |
Every day at midnight |
@weekly |
0 0 * * 0 |
Sunday at midnight |
@monthly |
0 0 1 * * |
First of the month at midnight |
@yearly (or @annually) |
0 0 1 1 * |
1 January at midnight |
@reboot |
(none) | Once, when the system starts |
They are convenient but hide the exact time, and they all run at midnight, which is when many other jobs run too. Spreading jobs across different minutes (for example 17 0 * * * instead of @daily) reduces load spikes.
Trap 1: an asterisk in the minute field
* 9 * * * looks like "at 9 o'clock", but the * in the minute field means every minute. The job runs 60 times, from 09:00 to 09:59. For once at 9:00, write 0 9 * * *.
Trap 2: day of month OR day of week
When both the day-of-month and day-of-week fields are restricted (neither is *), standard cron runs the job when either matches, not when both match.
0 9 13 * 5
This does not mean "Friday the 13th". It runs at 09:00 on the 13th of every month and at 09:00 every Friday. If you really need "only when both are true", schedule the job on one condition and test the other inside the command or script:
0 9 13 * * [ "$(date +\%u)" = "5" ] && /usr/local/bin/friday13.sh
date +%u prints the day of the week as 1 to 7 with Monday as 1, so 5 is Friday. The backslash before % is needed because cron treats an unescaped % in the command as a newline.
Trap 3: steps restart every period
A step does not mean "every N units since the last run". It means "every Nth value within this field's range", and it starts again at the beginning of each larger period.
*/7in the day-of-month field gives the 1st, 8th, 15th, 22nd and 29th, then the 1st of next month again. The gap at the month boundary is only two or three days, not seven.*/25in the minute field gives minutes 0, 25 and 50, then 0 again: gaps of 25, 25 and 10 minutes.- "Every 90 minutes" cannot be written as a single expression. You need two lines:
0 0-21/3 * * *(00:00, 03:00, … 21:00) and30 1-22/3 * * *(01:30, 04:30, … 22:30). Together they give a run every 90 minutes through the day.
If you need a truly regular interval that does not divide evenly into an hour or a day, a scheduler that supports intervals, or a script that checks how long it has been since the last run, is a better fit than cron.
Trap 4: "the last day of the month"
Standard cron has no symbol for the last day of the month. The usual workaround runs on every possible last day and checks whether tomorrow is the 1st:
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/month-end.sh
This uses the GNU version of date found on most Linux systems. Some other schedulers, described below, support an L symbol for this instead.
Time zones and daylight saving
Classic cron uses the time zone of the machine it runs on. Cloud servers and containers are very often set to UTC, so 0 9 * * * may run at 09:00 UTC, which is 10:00 in London in summer, 11:00 in Berlin in summer and 05:00 in New York during daylight saving time. Many hosted schedulers also run in UTC by default; others let you set a time zone per job, and some cron versions read a CRON_TZ variable at the top of the crontab.
To translate a local business time into the server's zone, the time zone planner shows the same moment across several cities with daylight saving taken into account. Remember that the translation changes twice a year when the clocks change, unless the job's scheduler itself is set to your local zone.
Daylight saving changes add one more wrinkle when a server runs on local time:
- In spring, one hour is skipped. In most of the EU in 2026, clocks go from 02:00 straight to 03:00 local time on 29 March; in most of the US, the same happens on 8 March. A job scheduled for 02:30 on that night may be skipped, or run as soon as the clock jumps, depending on the cron implementation.
- In autumn, one hour happens twice (25 October 2026 in the EU, 1 November 2026 in the US). A job at 01:30 may run once or twice.
The simplest protection is to run servers on UTC, or to avoid scheduling important jobs between about 01:00 and 03:00 local time.
Other cron dialects
Many tools use "cron-like" syntax that differs from classic Unix cron:
- A seconds field. Some schedulers expect six fields, with seconds first:
0 30 2 * * *means 02:30:00. Pasting a five-field expression into one of these shifts every field by one position. - A year field at the end, in some cloud schedulers.
- Extra symbols:
?(no specific value, used in one of the two day fields),L(last),W(nearest weekday) and#(for example5#3, the third Friday of the month). - Different day numbering. In some dialects, day of week runs from 1 (Sunday) to 7 (Saturday) instead of 0 to 6.
Always check which dialect a tool expects before copying an expression from somewhere else.
Why a cron job does not run
- Check the expression. Paste it into the cron builder and confirm the next run times are what you expect, in the right time zone.
- Check that cron saw it. List the installed jobs (
crontab -l). Some cron versions ignore the last line of a crontab if the file does not end with a newline. - Check the logs. On many Linux systems, cron logs each run to the system log. Log timestamps are sometimes Unix timestamps; the timestamp converter turns them into readable dates in any time zone.
- Check the environment. Cron runs commands with a minimal environment: a short
PATH, no shell profile, and the user's home directory as the working directory. A script that works in your terminal can fail under cron because a command is not found. Use absolute paths for commands and files. - Capture the output. Append
>> /var/log/myjob.log 2>&1to the command so errors are written to a file instead of disappearing. - Check permissions. The script must be executable by the user whose crontab it is in.
Preventing overlapping runs
If a job sometimes takes longer than its interval (a backup that runs every 15 minutes but occasionally takes 20), two copies can run at once and interfere. On Linux, flock provides a simple lock:
*/15 * * * * flock -n /tmp/backup.lock /usr/local/bin/backup.sh
With -n, a new run exits immediately if the previous one still holds the lock.
FAQ
What is the shortest interval cron supports?
One minute, with * * * * *. For anything more frequent, use a long-running service or a scheduler with a seconds field.
Does 0 0 * * 7 work for Sunday?
On most systems, yes: both 0 and 7 mean Sunday. Using 0 or SUN is the most portable choice.
Is */1 different from *?
No. Both mean every value.
How do I run a job every two weeks?
Cron cannot count weeks from a starting date. Schedule it weekly and have the script check the week number (for example, run only in even ISO weeks with [ $(( $(date +\%V) \% 2 )) -eq 0 ]), or use a scheduler that supports intervals. Note that years with 53 ISO weeks break the strict alternation once every few years.