Cron Expressions Explained: Read and Write Schedules Without Mistakes
Cron is the scheduler behind Linux crontab, most hosting control panels, Kubernetes CronJobs, GitHub Actions and plenty of cloud services. Its expressions are short, like 0 9 * * 1-5, and easy to get almost right. A job that runs at the wrong hour, twice on the night the clocks change, or every Monday instead of the first Monday is a classic support ticket. Here is how to read and write cron schedules, and the traps to check for before you rely on one.
The five fields
A standard cron expression has five fields separated by spaces. In a crontab, the command to run follows them.
| Position | Field | Allowed values |
|---|---|---|
| 1 | Minute | 0–59 |
| 2 | Hour | 0–23 (24-hour clock) |
| 3 | Day of month | 1–31 |
| 4 | Month | 1–12, or JAN–DEC |
| 5 | Day of week | 0–6 with 0 = Sunday, or SUN–SAT (Linux cron also accepts 7 for Sunday) |
So 0 9 * * 1-5 reads: minute 0, hour 9, any day of the month, any month, Monday to Friday. In plain English, 09:00 on weekdays.
The special characters
*means every value, so*in the hour field is every hour.,makes a list:0 8,17 * * *runs at 08:00 and 17:00.-makes a range:1-5in the day-of-week field is Monday to Friday./sets a step:*/15in the minute field is 0, 15, 30 and 45, and0-23/2in the hour field is every other hour.
The POSIX standard only defines *, lists and ranges. Steps and names are extensions, but Linux cron supports both, and Kubernetes supports steps. Many crons also accept shortcuts: @hourly, @daily, @weekly, @monthly, @yearly and @reboot. GitHub Actions doesn't.
Schedules you can copy
| Expression | Runs |
|---|---|
*/15 * * * * | Every 15 minutes, at :00, :15, :30 and :45 |
0 * * * * | Every hour, on the hour |
30 2 * * * | Every day at 02:30 |
0 9 * * 1-5 | 09:00, Monday to Friday |
*/10 9-17 * * 1-5 | Every 10 minutes from 09:00 to 17:50 on weekdays |
0 */6 * * * | 00:00, 06:00, 12:00 and 18:00 |
0 3 * * 0 | 03:00 every Sunday |
0 0 1 * * | Midnight at the start of the 1st of each month |
0 0 1 1 * | Midnight on 1 January, once a year |
The traps
Day of month OR day of week
When both day fields are restricted, cron runs when either one matches. 0 9 1 * 1 doesn't mean "the 1st, if it's a Monday": it runs on the 1st of every month and on every Monday. To run on the first Monday only, schedule days 1 to 7 and let the command check the weekday:
0 9 1-7 * * [ "$(date +\%u)" = 1 ] && /path/to/script
The % sign
In a crontab, a % in the command turns into a new line unless you escape it as \%, which is why the example above writes \%u. Dates in backup file names are the usual victim.
Time zones
Cron uses the time zone of whatever runs it, and servers and cloud services are often set to UTC. On a UTC server, 0 9 * * 1-5 runs at 17:00 in Singapore, and in New York at 05:00 in summer but 04:00 in winter. To run at 09:00 on weekdays in Sydney, a UTC server needs 0 23 * * 0-4: 23:00 the evening before, Sunday to Thursday, and an hour earlier while Sydney is on daylight saving time. Rather than doing that sum, set the time zone where you can:
- cronie, the cron on Red Hat-family Linux, reads a
CRON_TZ=line in the crontab. - Kubernetes uses the controller manager's local time zone unless you set
.spec.timeZone.CRON_TZinside the schedule isn't supported. - GitHub Actions schedules run in UTC unless you add a time zone. The shortest interval is every 5 minutes, runs can be delayed when the service is busy (especially at the start of the hour), and scheduled workflows in public repositories are switched off after 60 days without activity.
Daylight saving
When the clocks change, a local time can happen twice or not at all. The cronie manual is explicit: jobs in the skipped hour don't run, and jobs in a repeated hour run twice. Other cron versions handle it differently. Keep important jobs away from about 01:00 to 03:00 local time, or run the server on UTC.
Steps that don't divide evenly
*/7 in the minute field runs at 0, 7, 14 and so on up to 56, then at 0 again, so the last gap is only 4 minutes. Steps restart every hour (or every day, in the hour field), so "every 90 minutes" needs two lines: 0 0-21/3 * * * and 30 1-22/3 * * *.
Other formats
Some schedulers use six or seven fields. Quartz, common in Java applications, puts seconds first and an optional year last, numbers the days of the week 1–7 starting from Sunday, and adds L (last), W (nearest weekday) and # (nth weekday, so 6#3 is the third Friday). Paste a Quartz expression into a Linux crontab and it will be rejected, or quietly mean something else. Always check which format your scheduler expects.
How to build and check a schedule with SAA Tool
- Open the Cron Expression Generator and choose how often under Run: every few minutes or hours, every day, on certain days of the week, every month, every year, or Custom (edit each field).
- Set At (time) and tick the Days you want; Monday to Friday are ticked to start with. The expression, a plain-English description and the next five runs in your time zone update as you go.
- Click Copy for the expression, or Copy line for a full crontab line ending in
/path/to/command, and swap in your real command. - To check an expression someone else wrote, paste it into the Cron Expression Explainer, or tap a preset such as Weekdays at 9:00. Set Show times in to UTC to see when a UTC server will really run it, and How many runs to 10 or 20 to spot odd gaps.
- If a log records runs as Unix timestamps, paste one into the Timestamp Converter to see it in your time zone and UTC side by side. Unix timestamps explained has more.
Both cron tools run entirely in your browser. The Explainer accepts the @ shortcuts, ignores the first (seconds) field of a six-part expression, and rejects Quartz's L, W and #, because standard cron doesn't support them. It also tells you when a schedule can never run, such as 0 0 30 2 * (30 February).
Quick answers
- How do I run a job on the last day of the month? Standard cron has no "last day". Run on the 28th to 31st and check that tomorrow is the 1st:
0 18 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /path/to/script(this uses GNU date, as on most Linux systems). - Why does my job work by hand but not from cron? Cron starts with a bare environment:
/bin/shas the shell, a short PATH and none of your login settings. Use full paths to commands and files, and send output to a log with>> /tmp/myjob.log 2>&1at the end of the line. - Can cron run every 30 seconds? Not standard cron; its smallest unit is one minute.
- Is Sunday 0 or 7? Linux cron accepts both, and 0 works everywhere that uses standard cron. Quartz uses 1.
- Why did nothing run? Check that the cron service is running, look for cron entries in the system log, and confirm the next runs in the Explainer are what you expect.
Sources
Spotted a mistake or something out of date? Tell us and we'll fix it.