Cron is the Unix job scheduler that has been quietly running scripts, sending reports, rotating logs, and triggering backups on servers around the world since 1975. If you've worked with Linux, cloud hosting, CI/CD pipelines, GitHub Actions, or almost any web hosting control panel, you've encountered cron, or you will soon.
The syntax looks intimidating at first glance: 0 3 * * 1 doesn't exactly announce itself as "every Monday at 3 AM." But cron expressions follow a simple, consistent structure, and once it clicks, you can read and write any schedule in seconds. This guide covers everything you need: the five fields, every special character, the most common real-world schedules, and the mistakes that catch people out.
The Five Fields
A standard cron expression has five space-separated fields, each representing a unit of time. From left to right:
┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12)
│ │ │ │ ┌───── day of week (0–6, Sunday = 0)
│ │ │ │ │
* * * * * command to execute
An asterisk (*) in any position means "every valid value for this field." So * * * * * runs every minute of every hour of every day, the cron equivalent of an infinite loop. Everything else is a variation on restricting or combining values within those five fields.
Special Characters
Cron uses four special characters that let you express almost any schedule without needing multiple entries.
Asterisk * — Every value
Matches every possible value for the field. * * * * * runs every minute. 0 * * * * runs at the top of every hour.
Comma , — List of values
Lets you specify multiple discrete values. 0 9,17 * * * runs at 9:00 AM and 5:00 PM every day. * * * * 1,3,5 runs every minute on Monday, Wednesday, and Friday.
Hyphen - — Range of values
Defines a continuous range. 0 9-17 * * * runs at the top of every hour between 9 AM and 5 PM. * * * * 1-5 runs every minute on weekdays only.
Slash / — Step values
Runs at regular intervals. */15 * * * * runs every 15 minutes. 0 */6 * * * runs every 6 hours at the top of the hour (midnight, 6 AM, noon, 6 PM). You can also use steps on ranges: 0 8-18/2 * * * runs every two hours between 8 AM and 6 PM.
Common Real-World Cron Expressions
These cover the vast majority of scheduled tasks you'll encounter or need to write:
# Every minute
* * * * *
# Every 5 minutes
*/5 * * * *
# Every 15 minutes
*/15 * * * *
# Every hour (at :00)
0 * * * *
# Every day at midnight
0 0 * * *
# Every day at 3:30 AM
30 3 * * *
# Every Monday at 9 AM
0 9 * * 1
# Every weekday (Mon–Fri) at 8 AM
0 8 * * 1-5
# Every Sunday at midnight
0 0 * * 0
# First day of every month at midnight
0 0 1 * *
# Every quarter (Jan, Apr, Jul, Oct) on the 1st at midnight
0 0 1 1,4,7,10 *
# Once a year — January 1st at midnight
0 0 1 1 *
Named Shortcuts
Many cron implementations (including Vixie cron, the most common Linux variant) support named shortcuts as aliases for common expressions. These are worth knowing because they're more readable and less error-prone than typing the fields by hand:
@yearly → 0 0 1 1 * (once a year, January 1st at midnight)
@monthly → 0 0 1 * * (first day of every month at midnight)
@weekly → 0 0 * * 0 (every Sunday at midnight)
@daily → 0 0 * * * (every day at midnight)
@hourly → 0 * * * * (every hour at :00)
@reboot → (run once at system startup)
Support varies by platform. @reboot in particular isn't universal, so check your environment's documentation before relying on them in production.
Day of Month vs. Day of Week
This is the most common source of confusion in cron scheduling. When you specify both a day-of-month value and a day-of-week value (i.e. neither is *), most cron implementations treat it as an OR condition, not AND.
For example: 0 0 15 * 1 does not mean "the 15th of the month, but only if it's a Monday." It means "run on the 15th of every month OR every Monday, whichever comes first." If you actually want "the 15th only when it falls on a Monday," you need to handle that logic inside the script itself.
Timezone Gotchas
Standard cron runs in the server's local timezone, which is often UTC on cloud servers, even when that's not what you expect. A job set to run at 0 9 * * * on a server in UTC will fire at 9 AM UTC, which is 4 AM or 5 AM US Eastern depending on daylight saving time.
A few things to keep in mind:
- Check your server's timezone with
timedatectlorcat /etc/timezonebefore writing time-sensitive schedules. - Daylight saving time can cause jobs to run an hour early or late, or to skip or double-fire around the transition. Jobs that run at 2 AM are particularly vulnerable.
- Some cron variants (and most cloud schedulers like AWS EventBridge and GitHub Actions) let you set a timezone explicitly. Use it when it's available.
- Logging the actual execution time inside your script is good practice and makes DST-related surprises obvious.
Cron in Modern Environments
Traditional cron (crontab -e on a Linux server) is still widely used, but cron expression syntax has been adopted broadly across modern tooling:
- GitHub Actions: The
scheduletrigger uses standard 5-field cron syntax (always UTC). Example:- cron: '0 6 * * 1'runs every Monday at 6 AM UTC. - AWS EventBridge / CloudWatch Events: Uses a 6-field variant with an added year field, and uses
?instead of*for "no specific value" in day fields. - Kubernetes CronJobs: Standard 5-field syntax, with an optional
@` shorthand support depending on the controller. - cPanel / Plesk / DirectAdmin: GUI-based cron editors that generate standard cron expressions. It's useful to know the underlying syntax when the GUI doesn't cover your exact use case.
- Laravel, Django, Node (node-cron): Application-level schedulers that accept cron expressions directly in code, sometimes with a 6th field for seconds.
Common Mistakes to Avoid
Forgetting that day-of-week 0 and 7 are both Sunday. Most implementations accept 0–7 for day-of-week, where both 0 and 7 mean Sunday. Using 7 is technically valid but can cause confusion when reading expressions.
Using 0 0 * * 7 thinking it means Saturday. Saturday is 6, not 7. * * * * 7 is Sunday again.
Assuming */60 means "every 60 minutes." It doesn't. 60 is out of range for the minutes field (0–59), and behavior is undefined or an error depending on implementation. Use 0 * * * * for hourly.
Scheduling too many jobs at the same time. A common anti-pattern is starting all cron jobs at 0 * * * * (top of the hour) or 0 0 * * * (midnight). If you have ten jobs all starting simultaneously, stagger them, such as 5 0 * * *, 10 0 * * *, etc., to avoid resource spikes.
No output logging. By default, cron emails output to the local system user (which usually goes nowhere). Redirect stdout and stderr to a log file: 0 3 * * * /path/to/script.sh >> /var/log/myjob.log 2>&1
Cron is one of those tools that rewards a small upfront investment in understanding. The five-field structure is consistent everywhere it appears, and the special characters cover every real-world scheduling pattern you're likely to need. Get comfortable with it once and it becomes one of the fastest, most reliable parts of any automation stack.