Cron expressions are the backbone of automated task scheduling on Unix-like systems, and they have become a standard across cloud platforms, programming languages, and CI/CD pipelines worldwide. Whether you are setting up a nightly database backup, scheduling a daily report email, or configuring a recurring API health check, cron expressions are the language you need to speak. This guide covers everything from basic syntax to advanced patterns, with real-world examples you can copy and use immediately.
A cron expression is a string of five or six fields separated by spaces that defines a schedule for recurring tasks. Originally part of the Unix cron daemon (short for "chronograph"), the format has been adopted by Java's Quartz scheduler, Kubernetes CronJobs, AWS EventBridge, GitHub Actions, and countless other platforms. Despite minor variations between implementations, the core syntax remains consistent across virtually all systems.
The standard Unix cron expression has five fields representing different units of time:
| Field | Allowed Values | Description |
|---|---|---|
| Minute | 0-59 | The exact minute of the hour |
| Hour | 0-23 | The hour of the day (24-hour format) |
| Day of Month | 1-31 | The day of the month |
| Month | 1-12 (or JAN-DEC) | The month of the year |
| Day of Week | 0-7 (or SUN-SAT, 0 and 7 = Sunday) | The day of the week |
Some implementations add optional fields. The most common extension is a sixth field at the beginning for seconds (used by Quartz, AWS, and many cloud schedulers), or a sixth field at the end for the year (also Quartz). We will note these variations as they come up.
The power of cron expressions comes from a small set of operators that you combine within each field. Master these five operators and you can express virtually any schedule:
The asterisk matches every possible value for the field. */5 * * * * means "every 5 minutes" because the asterisk in the minute field combined with the step operator selects every fifth minute. Used alone, * means "every" — every minute, every hour, every day.
Separate specific values with commas to create a list. 0 9,12,18 * * * runs a task at 9:00 AM, 12:00 PM, and 6:00 PM every day. You can mix numbers and abbreviated names: 0 9 * * MON,FRI runs at 9:00 AM every Monday and Friday.
Define a range with a hyphen. 0 9-17 * * * runs every hour from 9:00 AM to 5:00 PM. Ranges are inclusive on both ends. You can combine ranges with lists: 0 9-11,13-17 * * * runs from 9-11 AM and 1-5 PM.
The slash specifies step increments within a range. */15 * * * * runs every 15 minutes (0, 15, 30, 45). 0-30/10 * * * * runs every 10 minutes, but only during the first 30 minutes of each hour (0, 10, 20, 30). The value before the slash is the start; if omitted, it defaults to the field's minimum value.
Unix cron provides several shorthand strings that replace full expressions:
| String | Equivalent | Meaning |
|---|---|---|
| @yearly (or @annually) | 0 0 1 1 * | Once a year, January 1st at midnight |
| @monthly | 0 0 1 * * | Once a month, on the 1st at midnight |
| @weekly | 0 0 * * 0 | Once a week, Sunday at midnight |
| @daily (or @midnight) | 0 0 * * * | Once a day, midnight |
| @hourly | 0 * * * * | Once an hour, at minute 0 |
| @reboot | N/A | Run once at system startup |
These shortcuts are supported on most Unix systems but may not work in cloud cron implementations like AWS or Kubernetes. When in doubt, use the full expression.
Here is a comprehensive collection of cron expressions for common scheduling scenarios. Copy and adapt these for your own use cases:
# Every minute
* * * * *
# Every 5 minutes
*/5 * * * *
# Every 30 minutes
*/30 * * * *
# Every hour at minute 0
0 * * * *
# Every 2 hours
0 */2 * * *
# Every 6 hours
0 */6 * * *
# Daily at midnight
0 0 * * *
# Daily at 6:30 AM
30 6 * * *
# Daily at 9 AM and 5 PM
0 9,17 * * *
# Every weekday (Mon-Fri) at 9 AM
0 9 * * 1-5
# Weekends only at 10 AM
0 10 * * 0,6
# Every weekday every 5 minutes during business hours
*/5 9-17 * * 1-5
# Every Sunday at 2:30 AM
30 2 * * 0
# Every January and July at midnight
0 0 * 1,7 *
# Twice daily: 8 AM and 8 PM
0 8,20 * * *
# Every 10 minutes between 9 AM and 5 PM on weekdays
*/10 9-17 * * 1-5
Now that you understand the syntax, here is how to actually put cron expressions to work on a real system.
On any Unix-like system, open your user's crontab by running:
crontab -e
This opens the crontab file in your default editor. Each line is a separate cron job in the format:
* * * * * /path/to/command arg1 arg2
For example, to run a Python backup script every night at 2 AM:
0 2 * * * /usr/bin/python3 /home/user/scripts/backup.py >> /home/user/logs/backup.log 2>&1
Cron jobs run in a minimal environment — they do not inherit your shell's PATH, environment variables, or aliases. This is the number one reason cron jobs fail silently. Always use absolute paths for commands and scripts, and explicitly set any environment variables your script needs.
# Set environment at the top of your crontab
PATH=/usr/local/bin:/usr/bin:/bin
PYTHONPATH=/home/user/myproject
NODE_ENV=production
# Then your jobs
0 2 * * * python3 /home/user/scripts/backup.py
0 * * * * node /home/user/app/health-check.js
Cron sends any output from your commands to your local mailbox by default. To capture logs to a file, redirect both stdout and stderr:
0 2 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1
To check whether your cron jobs are running, inspect the system cron log:
# On Ubuntu/Debian
grep CRON /var/log/syslog
# On CentOS/RHEL
grep CROND /var/log/cron
# List all cron jobs for your user
crontab -l
While Unix cron is the origin, cron expressions are now used across many platforms with slight variations. Here is what you need to know about the most popular implementations:
Quartz uses a six-field format with seconds as the first field and an optional year as the seventh:
Seconds Minutes Hours DayOfMonth Month DayOfWeek [Year]
0 0 12 * * ? # Every day at noon
0 0/5 14 * * ? # Every 5 minutes from 2-3 PM
Key difference: Quartz uses ? (instead of *) for the day-of-month or day-of-week field when you want to ignore one of them. You cannot use * in both fields simultaneously — one must be ?.
Kubernetes uses the standard five-field format but requires the ConcurrencyPolicy and StartingDeadlineSeconds configurations alongside the schedule:
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-report
spec:
schedule: "0 8 * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: report
image: myapp:latest
restartPolicy: OnFailure
AWS uses a six-field format with seconds at the beginning and optionally years at the end:
cron(0 10 * * ? *) # Every day at 10:00 AM UTC
cron(0/15 * * * ? *) # Every 15 minutes
cron(0 9 ? * 2-6 *) # Weekdays at 9 AM UTC
Important: AWS cron always requires the ? for either day-of-month or day-of-week, and the year field is mandatory (use * for every year). Also note that AWS cron evaluates in UTC unless you specify a timezone.
GitHub Actions uses a simplified cron syntax with five fields and supports ranges, steps, and asterisks:
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight UTC
env -i /path/to/command to simulate the cron environment.logger for syslog integration.% is treated as a newline character. Escape it with a backslash (\%) when used in commands like date +\%Y\%m\%d.ps aux or in system logs. Use environment files, secrets management tools, or config files with restricted permissions instead.
Cron expressions can be cryptic, especially for complex schedules. Here are practices that make them more maintainable:
0 2 * * * /opt/scripts/nightly-db-backup.sh is clearer than a long one-liner.crontab.guru to verify your expression means what you think it means.crontab -l > crontab-backup.txt.Systemd timers are the modern alternative to cron on Linux systems that use systemd. They offer advantages like dependency management, logging integration with journald, and support for monotonic timers (e.g., "10 minutes after boot"). However, cron remains more universally supported and is simpler for basic schedules. Many systems use both — cron for user-level tasks and systemd timers for system services.
Standard Unix cron has a minimum resolution of one minute — it cannot run tasks every second. For sub-minute scheduling, use a while loop in a script, systemd timers with AccuracySec, or a dedicated scheduler like Quartz (Java) or APScheduler (Python). Be cautious with second-level scheduling, as it can create significant system load if the task is resource-intensive.
The most common reasons: (1) The PATH is wrong — use absolute paths for all commands. (2) The script lacks execute permissions — run chmod +x script.sh. (3) The cron daemon is not running — check with systemctl status cron. (4) There is a syntax error in the expression — validate with an online tool. (5) The user does not have permission — check /etc/cron.allow and /etc/cron.deny.
Standard cron does not account for DST. Jobs scheduled for 2:30 AM may run once, twice, or not at all during a DST transition depending on the direction of the change. For critical scheduling, use UTC-based scheduling (which has no DST) and convert to local time in your script if needed, or use a library like pytz or moment-timezone in application-level schedulers.
In Quartz scheduler, ? means "no specific value" and is used exclusively in the day-of-month or day-of-week field. You must use ? in one of these two fields (not both) because Quartz requires you to explicitly indicate which day field you are not specifying. This is different from standard Unix cron, where * serves this purpose.
Use online validators like crontab.guru to verify the expression describes the schedule you expect. To test the actual command, run it manually in your shell first. For a more thorough test, temporarily set the cron schedule to run every minute (* * * * *), verify it works, then change it back to the intended schedule.
Cron expressions are one of the most widely used scheduling mechanisms in computing, and understanding them thoroughly pays dividends across every layer of the technology stack. From a simple crontab on a single server to complex cloud-native scheduling pipelines, the syntax and principles remain the same. Bookmark this guide, practice with the examples, and you will never struggle with cron scheduling again.