Cron Expressions Explained: Syntax, Examples and Common Mistakes
Learn to read and write cron expressions with clear examples, a field-by-field breakdown, and the mistakes that make scheduled jobs silently fail.
· 7 min read
Contents
- The five fields
- Special characters
- 15 cron expressions you will actually use
- The day-of-month vs day-of-week trap
- Time zones
- Editing your crontab on Linux
- Common mistakes (and how to fix them)
- 1. The job works in the terminal but not in cron
- 2. Unescaped percent signs
- 3. No output, no idea what happened
- 4. Confusing /N with "every N from now"
- 5. Overlapping runs
- Cron in other tools
- FAQ
- What does mean?
- How do I run a cron job every 30 seconds?
- Is Sunday 0 or 7?
- How can I test a cron expression without waiting?
- Further reading
Cron is the scheduler that has been running jobs on Unix systems since the 1970s, and its syntax shows up everywhere today: Linux crontabs, GitHub Actions, Kubernetes CronJobs, cloud schedulers and most CI tools. Once you can read the five fields, you can read all of them.
This guide walks through the syntax, gives you copy-paste examples for the schedules people actually need, and covers the mistakes that cause jobs to run at the wrong time — or never. If you just want to check an expression, paste it into our free Cron Expression Explainer.
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–7 or SUN–SAT, 0 and 7 are Sunday)
│ │ │ │ │
* * * * * command-to-run
The job runs when the current time matches every field — with one important exception for the two day fields, covered below.
Special characters
| Character | Meaning | Example | Reads as |
|---|---|---|---|
* | Every value | * * * * * | Every minute |
, | List of values | 0 9,17 * * * | At 09:00 and 17:00 |
- | Range | 0 9 * * 1-5 | At 09:00, Monday to Friday |
/ | Step | */15 * * * * | Every 15 minutes |
Steps can be combined with ranges: 0-30/10 in the minute field means minutes 0, 10, 20 and 30.
Many implementations also accept shortcuts:
| Shortcut | Equivalent |
|---|---|
@hourly | 0 * * * * |
@daily / @midnight | 0 0 * * * |
@weekly | 0 0 * * 0 |
@monthly | 0 0 1 * * |
@yearly / @annually | 0 0 1 1 * |
@reboot | Once at startup (Linux cron only) |
15 cron expressions you will actually use
*/5 * * * * Every 5 minutes
0 * * * * Every hour, on the hour
0 */6 * * * Every 6 hours (00:00, 06:00, 12:00, 18:00)
0 0 * * * Every day at midnight
30 2 * * * Every day at 02:30
0 9 * * 1-5 Weekdays at 09:00
0 9-17 * * 1-5 Every hour from 09:00 to 17:00 on weekdays
0 0 * * 0 Every Sunday at midnight
0 0 * * 6,0 Saturdays and Sundays at midnight
0 0 1 * * First day of every month at midnight
0 0 1 */3 * First day of every quarter
0 0 1 1 * Once a year, on January 1st
15 14 1 * * 14:15 on the 1st of every month
0 22 * * 1-5 Weeknights at 22:00
0 0 L * * Last day of month (Quartz-style schedulers only — not standard cron)
You can paste any of these into the cron explainer tool to confirm the next run times.
The day-of-month vs day-of-week trap
This is the rule that surprises almost everyone. When both the day-of-month and day-of-week fields are restricted (neither is *), standard cron runs the job when either one matches, not both.
0 0 13 * 5
You might read that as "midnight on Friday the 13th". Classic cron reads it as "midnight on the 13th of every month and midnight every Friday". If you need a true "Friday the 13th" schedule, run the job on the 13th and check the weekday inside the script:
0 0 13 * * [ "$(date +\%u)" = 5 ] && /path/to/script.sh
Time zones
Cron uses the time zone of the machine (or the scheduler service) running it. A few practical consequences:
- Servers usually run in UTC. A job set to
0 9 * * *on a UTC server runs at 9am UTC, which is 4am or 5am in New York depending on daylight saving time. - GitHub Actions schedules are always in UTC.
- Daylight saving changes can skip or double-run jobs scheduled between 01:00 and 03:00 local time on systems that do not run in UTC. Schedule important jobs outside that window, or keep servers on UTC.
Check what time zone your system uses with:
timedatectl # systemd-based Linux
date +%Z # any Unix
Editing your crontab on Linux
crontab -e # edit your user's crontab
crontab -l # list current jobs
crontab -r # remove ALL your jobs (careful: no confirmation)
Each line is one job. A real-world entry that backs up a database every night and logs the output looks like this:
30 2 * * * /home/deploy/bin/backup-db.sh >> /var/log/backup-db.log 2>&1
Common mistakes (and how to fix them)
1. The job works in the terminal but not in cron
Cron runs with a minimal environment: a short PATH, no shell profile, and often a different working directory. Use absolute paths for every command and file, or set PATH at the top of the crontab:
PATH=/usr/local/bin:/usr/bin:/bin
2. Unescaped percent signs
In a crontab, % is turned into a newline. Commands such as date +%Y-%m-%d must escape it as \%:
0 1 * * * tar czf /backups/site-$(date +\%F).tar.gz /var/www
3. No output, no idea what happened
Redirect both stdout and stderr to a log file (>> file.log 2>&1) so failures leave a trace. Without it, output is mailed to the local user — which on most servers means it disappears.
4. Confusing */N with "every N from now"
*/20 in the minute field means minutes 0, 20 and 40 of each hour — it is aligned to the clock, not to when you saved the crontab.
5. Overlapping runs
If a job sometimes takes longer than its interval, two copies can run at once. Use flock to prevent that:
*/5 * * * * flock -n /tmp/sync.lock /usr/local/bin/sync.sh
Cron in other tools
| Platform | Fields | Notes |
|---|---|---|
| Linux crontab | 5 | Machine time zone |
| GitHub Actions | 5 | Always UTC; shortest interval is 5 minutes |
| Kubernetes CronJob | 5 | Supports a timeZone field |
| Quartz / Spring | 6–7 | Adds seconds (and optional year), supports L, W, #, ? |
| AWS EventBridge | 6 | Adds year; uses ? for one of the day fields |
If an expression has six or seven fields, you are looking at a Quartz-style or cloud-provider dialect, not standard cron.
FAQ
What does * * * * * mean?
Run every minute of every hour of every day.
How do I run a cron job every 30 seconds?
Standard cron cannot go below one minute. Either run two entries, one with sleep 30 before the command, or use a systemd timer, which supports second-level precision.
Is Sunday 0 or 7?
Both. Most cron implementations accept 0 and 7 for Sunday, and SUN works as well.
How can I test a cron expression without waiting?
Paste it into a cron explainer to see the next run times, and run the command manually with env -i to mimic cron's minimal environment.
Further reading
- The
crontab(5)manual page: runman 5 crontabon any Linux system - GitHub Actions schedule syntax
- Kubernetes CronJob documentation
- Tool: Unix timestamp converter
- Next: Git commands every developer should know
Keep reading
Bash Scripting Basics: A Practical Guide for Developers
Write reliable Bash scripts: shebang and strict mode, variables, quoting, conditionals, loops, functions, arguments and exit codes, with a complete example script.
SSH Keys Explained: Generate, Add and Use Them Safely
Create an SSH key with ssh-keygen, add it to a server and to GitHub, use ssh-agent and a config file, and fix the most common SSH permission errors.
Makefiles for Modern Projects: A Practical Tutorial
Use a Makefile as your project's command menu: targets, phony targets, variables, dependencies, a self-documenting help target and a real-world example.