Cron is the job scheduler of Unix systems, and its syntax has crept in everywhere: Linux servers, Kubernetes, GitHub Actions, serverless functions, Laravel, Spring… Learning it takes ten minutes. Forgetting it takes even less. This cheat sheet is meant to be kept close at hand.
The five fields
A classic cron expression has five space-separated fields:
┌───────────── 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 Sunday)
│ │ │ │ │
* * * * * command
Each field accepts these operators:
| Symbol | Meaning | Example | Reads as |
|---|---|---|---|
* |
any value | * * * * * |
every minute |
, |
list | 0 9,14 * * * |
at 9:00 and 14:00 |
- |
range | 0 9-17 * * * |
every hour on the hour, 9 to 17 |
/ |
step | */15 * * * * |
every 15 minutes |
20 examples you'll need
| Expression | When it runs |
|---|---|
* * * * * |
Every minute |
*/5 * * * * |
Every 5 minutes |
*/15 * * * * |
Every 15 minutes (:00, :15, :30, :45) |
0 * * * * |
Every hour, on the hour |
30 * * * * |
Every hour, at half past |
0 */2 * * * |
Every 2 hours |
0 0 * * * |
Every day at midnight |
0 8 * * * |
Every day at 8:00 |
30 23 * * * |
Every day at 23:30 |
0 9 * * 1-5 |
Monday to Friday at 9:00 |
0 10 * * 6,0 |
Saturdays and Sundays at 10:00 |
0 9-18 * * 1-5 |
Every hour from 9 to 18 on weekdays |
0 0 * * 0 |
Every Sunday at midnight |
0 0 1 * * |
On the 1st of every month |
0 0 15 * * |
On the 15th of every month |
0 0 1 1 * |
On January 1 (once a year) |
0 0 1 */3 * |
On the first day of every quarter |
0 3 * * 1 |
Mondays at 3:00 (a classic for backups) |
*/10 8-20 * * * |
Every 10 minutes between 8:00 and 20:59 |
0 12 1-7 * 1 |
⚠️ Not "the first Monday of the month" (see below) |
If you're unsure about a specific expression, paste it into the cron parser: it lists the next dates it will run. And if you'd rather build it without typing the syntax, the cron builder does it with dropdowns and explains it in plain English.
The @ shortcuts
Linux cron (and most of its descendants) accepts readable aliases; GitHub Actions and other cloud schedulers don't:
| Alias | Equivalent |
|---|---|
@yearly or @annually |
0 0 1 1 * |
@monthly |
0 0 1 * * |
@weekly |
0 0 * * 0 |
@daily or @midnight |
0 0 * * * |
@hourly |
0 * * * * |
@reboot |
At system startup |
The 5 mistakes that break the most jobs
1. Day of month and day of week are combined with OR, not AND
This is the most famous trap. In classic cron, if you restrict both the day of month and the day of week, the job runs when either matches. So 0 12 1-7 * 1 doesn't mean "the first Monday of the month at 12:00", but "days 1 to 7 of every month and also every Monday".
The usual fix is to keep only one of the two fields and filter inside the command:
0 12 1-7 * * [ "$(date +\%u)" = 1 ] && /path/script.sh
(In a crontab, % must be escaped as \%: without the backslash, cron treats it as a newline.)
2. The time zone isn't the one you think
Cron uses the time zone of the system it runs on. On a server set to UTC, 0 9 * * * runs at 9:00 UTC, which is 4:00 or 5:00 in New York depending on daylight saving time. Managed schedulers usually run in UTC: GitHub Actions always uses UTC, and Kubernetes CronJobs use the controller's time zone unless you set timeZone in the spec.
3. Daylight saving time changes
If you schedule something between 2:00 and 3:00 a.m., on the day the clocks change it may be skipped (in spring that hour doesn't exist) or, depending on the implementation, run twice (in autumn it repeats). For critical jobs, avoid that window or work in UTC.
4. Steps don't carry over between hours
*/7 * * * * doesn't run "every 7 minutes" in the strict sense: it runs at minutes 0, 7, 14… 56, and the next hour it starts again at 0. Between minute 56 and 0 only 4 minutes pass. For intervals that don't divide 60, cron isn't the right tool.
5. Five fields or six
Classic cron has five fields and one-minute resolution. Quartz, Spring (@Scheduled) and libraries like node-cron add a sixth field at the start for seconds: 0 */5 * * * * means "every 5 minutes" in Spring, but it's an invalid expression in a crontab. When you copy an expression from one system to another, count the fields.
How to check an expression before deploying
The most reliable method is to look at the real upcoming runs. Paste the expression into the cron parser, choose 5 or 6 fields depending on your system and check the list of dates: if your "first Monday of the month" also runs on the 3rd, you've fallen into mistake number 1. Everything is calculated in your browser, so you can try as many expressions as you want.

