Cron is one of the oldest and most reliable scheduling mechanisms in computing. Originally developed for Unix in the 1970s, cron allows system administrators and developers to schedule commands or scripts to run automatically at specified intervals. The name "cron" comes from the Greek word "chronos," meaning time.
At the heart of cron is the cron expression -- a compact string that defines precisely when a task should execute. A cron expression like 0 9 * * 1-5 might look cryptic at first glance, but it encodes a precise schedule: "at 9:00 AM, Monday through Friday." Once you learn the syntax, you can express virtually any recurring schedule in a single line.
While cron originated in Unix, its expression format has been adopted far beyond the terminal. Today, cron expressions are used in Kubernetes CronJobs, GitHub Actions workflows, AWS EventBridge rules, Jenkins build triggers, Apache Airflow DAGs, and dozens of other platforms. Understanding cron syntax is a fundamental skill for any developer, DevOps engineer, or system administrator.
In this guide, we will walk through the 5-field cron format, explain every special character, provide over 20 practical examples, cover how cron works across different tools, and share best practices and common mistakes to avoid.
A standard cron expression consists of five fields separated by spaces. Each field represents a unit of time, and together they define a recurring schedule. The fields, from left to right, are:
+---------------- 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)
| | | | |
* * * * *
Let us break down each field:
The first field specifies the minute of the hour when the task should run. Valid values range from 0 to 59. A value of 0 means the task runs at the top of the hour (e.g., 9:00), while 30 means it runs at the half-hour mark (e.g., 9:30). An asterisk (*) means the task runs every minute.
The second field specifies the hour using 24-hour format. Valid values range from 0 (midnight) to 23 (11 PM). For example, 9 means 9:00 AM and 17 means 5:00 PM. An asterisk means every hour.
The third field specifies the day of the month. Valid values range from 1 to 31. Note that not all months have 31 days -- if you specify day 31, the task will not run in months with fewer days (like February, April, June, September, and November). An asterisk means every day of the month.
The fourth field specifies the month. You can use numbers (1-12) or three-letter abbreviations (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC). An asterisk means every month.
The fifth field specifies the day of the week. Both 0 and 7 represent Sunday. You can use numbers (0-7) or three-letter abbreviations (SUN, MON, TUE, WED, THU, FRI, SAT). An asterisk means every day of the week.
When both the day-of-month and day-of-week fields are specified (i.e., neither is *), the behavior varies by implementation. In standard Unix crontab, the task runs when either field matches. In some other systems like Quartz Scheduler, one field must use ? to indicate "no specific value."
Cron expressions support several special characters that let you create sophisticated schedules beyond simple fixed values. Understanding these characters is key to mastering cron syntax.
The asterisk matches every possible value for a field. In the minute field, * means every minute (0 through 59). In the month field, it means every month (January through December). The expression * * * * * runs every minute of every hour of every day.
The slash defines step (interval) values. It is used as range/step or */step. For example:
*/5 in the minute field means "every 5 minutes" (0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55)*/2 in the hour field means "every 2 hours" (0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22)10-30/5 in the minute field means "every 5 minutes from minute 10 through 30" (10, 15, 20, 25, 30)The hyphen defines a range of values. For example:
1-5 in the day-of-week field means Monday through Friday9-17 in the hour field means 9 AM through 5 PM1-15 in the day-of-month field means the 1st through the 15thThe comma separates individual values in a list. For example:
1,15 in the day-of-month field means the 1st and 15th of the month9,17 in the hour field means 9 AM and 5 PM1,3,5 in the day-of-week field means Monday, Wednesday, and Friday1,4,7,10 in the month field means January, April, July, and October (quarterly)
The question mark is used in the day-of-month and day-of-week fields to indicate "no specific value." It is primarily used in extended cron implementations like Quartz Scheduler and AWS EventBridge, where specifying both day fields creates ambiguity. In standard Unix crontab, ? is not typically used -- an asterisk serves the same purpose.
For example, in Quartz: 0 9 15 * ? means "at 9:00 AM on the 15th of every month, regardless of what day of the week it is."
Here are over 20 practical cron expression examples with explanations. These cover the most common scheduling scenarios you will encounter.
# Every minute
* * * * *
# Every 5 minutes
*/5 * * * *
# Every 10 minutes
*/10 * * * *
# Every 15 minutes
*/15 * * * *
# Every 30 minutes
*/30 * * * *
# Every minute between 9 AM and 5 PM
* 9-17 * * *
# Every hour (at minute 0)
0 * * * *
# Every 2 hours
0 */2 * * *
# Every 3 hours
0 */3 * * *
# Every 6 hours (at midnight, 6 AM, noon, 6 PM)
0 */6 * * *
# Every hour from 9 AM to 5 PM
0 9-17 * * *
# Every day at midnight
0 0 * * *
# Every day at 6:30 AM
30 6 * * *
# Every day at 9:00 AM
0 9 * * *
# Every day at 2:30 PM
30 14 * * *
# Every day at 11:45 PM
45 23 * * *
# Twice daily at 9 AM and 5 PM
0 9,17 * * *
# Three times daily at 8 AM, noon, and 6 PM
0 8,12,18 * * *
# Every Monday at 9 AM
0 9 * * 1
# Every Friday at 5 PM
0 17 * * 5
# Every Sunday at midnight
0 0 * * 0
# Every weekday (Mon-Fri) at 9 AM
0 9 * * 1-5
# Every weekend (Sat-Sun) at 10 AM
0 10 * * 0,6
# Every Wednesday and Friday at 3 PM
0 15 * * 3,5
# 1st of every month at midnight
0 0 1 * *
# 15th of every month at noon
0 12 15 * *
# 1st and 15th of every month at 9 AM
0 9 1,15 * *
# Last day approach: 28th of every month at 6 PM
0 18 28 * *
# Quarterly: 1st of Jan, Apr, Jul, Oct at midnight
0 0 1 1,4,7,10 *
# Annually: January 1st at midnight
0 0 1 1 *
# Every March 15th at 9 AM
0 9 15 3 *
# Every 5 minutes during business hours on weekdays
*/5 9-17 * * 1-5
# At minute 0 and 30 of every hour on weekdays
0,30 * * * 1-5
# Every hour on the 1st and 15th of the month
0 * 1,15 * *
# At 9 AM on the 1st Monday of each month (approximation)
0 9 1-7 * 1
Most cron implementations allow you to use three-letter abbreviations instead of numbers for months and days of the week. This makes expressions more readable at a glance.
| Number | Abbreviation | Month |
|---|---|---|
| 1 | JAN | January |
| 2 | FEB | February |
| 3 | MAR | March |
| 4 | APR | April |
| 5 | MAY | May |
| 6 | JUN | June |
| 7 | JUL | July |
| 8 | AUG | August |
| 9 | SEP | September |
| 10 | OCT | October |
| 11 | NOV | November |
| 12 | DEC | December |
| Number | Abbreviation | Day |
|---|---|---|
| 0 or 7 | SUN | Sunday |
| 1 | MON | Monday |
| 2 | TUE | Tuesday |
| 3 | WED | Wednesday |
| 4 | THU | Thursday |
| 5 | FRI | Friday |
| 6 | SAT | Saturday |
Using names makes expressions more self-documenting. Compare 0 9 * * 1-5 with 0 9 * * MON-FRI -- both are equivalent, but the latter is immediately clear to anyone reading it. Names are case-insensitive in most implementations, so MON, Mon, and mon all work.
Certain scheduling patterns come up repeatedly in real-world applications. Here are the most common ones with ready-to-use expressions.
Running cleanup tasks during off-peak hours minimizes impact on production workloads. A common pattern is daily at 2 AM:
0 2 * * *
Database backups are typically scheduled during low-traffic periods. For daily backups at 3 AM and weekly full backups on Sunday:
# Daily incremental backup at 3 AM
0 3 * * *
# Weekly full backup on Sunday at 1 AM
0 1 * * 0
Health check endpoints are often polled at short intervals to detect outages quickly:
# Every 2 minutes
*/2 * * * *
# Every 5 minutes during business hours on weekdays
*/5 9-18 * * 1-5
Business reports are commonly generated daily, weekly, or monthly:
# Daily report at 7 AM (before business hours)
0 7 * * *
# Weekly summary every Monday at 8 AM
0 8 * * 1
# Monthly report on the 1st at 6 AM
0 6 1 * *
Digest emails are typically sent at predictable intervals:
# Daily digest at 8 AM
0 8 * * *
# Weekly digest every Friday at 4 PM
0 16 * * 5
Caches are often cleared or refreshed on a schedule:
# Clear cache every 4 hours
0 */4 * * *
# Refresh search index every hour
0 * * * *
While the standard 5-field cron format is the most widely used, some platforms support extended formats with additional fields.
Some implementations like Quartz Scheduler (Java), Spring Framework, and certain cloud services add a seconds field as the first position:
# 6-field format: second minute hour day-of-month month day-of-week
0 */5 * * * * # Every 5 minutes (at second 0)
30 0 9 * * * # At 9:00:30 AM every day
0 0 12 * * MON # At noon every Monday
The seconds field works identically to the minutes field but with the same 0-59 range. Adding seconds provides sub-minute precision for applications that need it.
Quartz Scheduler also supports a 7-field format that adds a year field:
# 7-field: second minute hour day-of-month month day-of-week year
0 0 9 1 1 ? 2027 # At 9:00 AM on January 1st, 2027
The year field is optional and rarely used. Most scheduling systems do not support it, as recurring schedules typically should not be year-specific.
Some cron implementations support convenient shorthand strings:
@yearly or @annually = 0 0 1 1 * (once a year, Jan 1st)
@monthly = 0 0 1 * * (once a month, 1st day)
@weekly = 0 0 * * 0 (once a week, Sunday)
@daily or @midnight = 0 0 * * * (once a day, midnight)
@hourly = 0 * * * * (once an hour)
@reboot = (run once at startup)
These shorthands are supported by Unix crontab and some other tools, but not universally. Kubernetes CronJobs, GitHub Actions, and AWS EventBridge do not support them -- always use the standard 5-field format for maximum compatibility.
Recommendation: Unless you specifically need seconds-level precision, stick with the standard 5-field format. It is supported everywhere and avoids confusion between implementations.
Cron expressions are used across many platforms and tools. While the core syntax is consistent, each tool has its own nuances and configuration format.
The original cron implementation. Edit the crontab with crontab -e and define tasks with a cron expression followed by the command:
# Run backup script every day at 2 AM
0 2 * * * /usr/local/bin/backup.sh
# Rotate logs every Sunday at midnight
0 0 * * 0 /usr/sbin/logrotate /etc/logrotate.conf
# Clear temp files every hour
0 * * * * find /tmp -type f -mtime +7 -delete
Crontab supports environment variables like MAILTO, PATH, and SHELL at the top of the file. The system crontab at /etc/crontab adds a username field between the cron expression and the command.
Kubernetes CronJobs use standard 5-field cron expressions in the spec.schedule field. They create Job resources on a recurring schedule:
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: backup-tool:latest
command: ["/bin/sh", "-c", "pg_dump mydb > /backup/db.sql"]
restartPolicy: OnFailure
Kubernetes CronJobs support the concurrencyPolicy field (Allow, Forbid, Replace) to control what happens when the next scheduled run starts while the previous one is still running. The startingDeadlineSeconds field defines how long a job can be delayed before it is considered missed.
Important: Kubernetes uses UTC timezone by default. Starting with Kubernetes 1.27, you can set the timeZone field on CronJob specs (e.g., timeZone: "America/New_York").
GitHub Actions supports cron expressions in the schedule event trigger. Schedules always run in UTC:
name: Scheduled Tests
on:
schedule:
# Run tests every day at 6 AM UTC
- cron: '0 6 * * *'
# Run security scan every Monday at midnight UTC
- cron: '0 0 * * 1'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
GitHub Actions cron schedules have a minimum interval of 5 minutes. Runs may be delayed during periods of high load. You can define multiple cron entries under schedule to trigger the same workflow at different times.
AWS EventBridge supports cron expressions with a 6-field format (adding year). The ? character is required in either day-of-month or day-of-week:
# EventBridge cron format (6 fields):
# minute hour day-of-month month day-of-week year
# Every day at 9 AM UTC
cron(0 9 * * ? *)
# Every Monday at noon UTC
cron(0 12 ? * MON *)
# First day of every month at midnight
cron(0 0 1 * ? *)
# Every 15 minutes on weekdays
cron(0/15 * ? * MON-FRI *)
EventBridge cron expressions are always evaluated in UTC. Note the required cron() wrapper and the year field. AWS also supports rate expressions like rate(5 minutes) for simpler intervals.
Jenkins uses cron syntax for build triggers in the "Build periodically" and "Poll SCM" configurations:
# Build every weekday at 8 AM
0 8 * * 1-5
# Poll SCM every 5 minutes
*/5 * * * *
# Nightly build at 2 AM
0 2 * * *
Jenkins adds a hash (H) symbol that distributes load by hashing the job name. H/15 * * * * runs every 15 minutes but at a consistent offset unique to each job, preventing all jobs from running at the same time. This is a Jenkins-specific extension, not standard cron.
Airflow DAGs use cron expressions or preset strings in the schedule_interval parameter (Airflow 1.x) or schedule parameter (Airflow 2.x):
from airflow import DAG
from datetime import datetime
dag = DAG(
'daily_etl',
schedule='0 6 * * *', # Every day at 6 AM
start_date=datetime(2026, 1, 1),
catchup=False
)
Airflow also supports preset strings like @daily, @hourly, @weekly, and timetable objects for more complex scheduling logic.
Following these best practices will help you create reliable, maintainable, and well-behaved cron schedules.
Scheduling all your jobs at midnight (0 0 * * *) creates a "thundering herd" problem where many tasks compete for resources simultaneously. Stagger your schedules across different minutes and hours. Instead of running four daily jobs all at midnight, spread them out:
# Bad: everything at midnight
0 0 * * * /scripts/backup.sh
0 0 * * * /scripts/report.sh
0 0 * * * /scripts/cleanup.sh
0 0 * * * /scripts/sync.sh
# Good: staggered schedules
0 1 * * * /scripts/backup.sh
0 2 * * * /scripts/cleanup.sh
30 5 * * * /scripts/report.sh
0 6 * * * /scripts/sync.sh
Always add comments to your crontab entries explaining what the job does and why it runs at that time. Future maintainers (including yourself) will appreciate it:
# Database backup - runs at 2 AM to avoid peak traffic
# Contact: [email protected] | Ticket: OPS-1234
0 2 * * * /usr/local/bin/db-backup.sh >> /var/log/backup.log 2>&1
One of the most common sources of cron-related bugs is timezone confusion. Know which timezone your cron daemon uses:
TZ=America/New_York in the crontab for per-entry timezone control.timeZone field (v1.27+).When in doubt, use UTC and document that your schedule is in UTC. Be especially careful around daylight saving time transitions, which can cause jobs to run twice or not at all.
Always redirect both stdout and stderr to a log file. Silent failures are the bane of cron jobs:
0 2 * * * /scripts/task.sh >> /var/log/task.log 2>&1
If a cron job might run longer than its interval, use flock or similar mechanisms to prevent overlapping executions:
*/5 * * * * flock -n /tmp/task.lock /scripts/long-task.sh
Prevent runaway jobs by setting timeouts:
0 2 * * * timeout 3600 /scripts/backup.sh >> /var/log/backup.log 2>&1
Use monitoring services like Healthchecks.io, Cronitor, or PagerDuty to alert you when cron jobs fail or do not run on schedule. These services provide a unique URL that your script pings on success -- if the ping does not arrive within the expected window, you get alerted.
Before deploying a new cron schedule, verify it does what you expect. Use our Cron Expression Parser to see the next run times and confirm the schedule matches your intent. A misplaced field can mean the difference between "every day at 9 AM" and "every minute on the 9th of every month."
Even experienced engineers make mistakes with cron expressions. Here are the most common pitfalls and how to avoid them.
The most common mistake is getting the field order wrong. "Every day at 9:30 AM" is 30 9 * * *, not 9 30 * * *. The minute comes first, then the hour. A helpful mnemonic: the fields go from smallest unit (minute) to largest (day of week), with minute and hour being the first two.
In cron, both 0 and 7 represent Sunday. Using 0 9 * * 7 and 0 9 * * 0 produce identical schedules. This is a common source of off-by-one errors when manually calculating day numbers.
A job scheduled at 0 2 * * * in a timezone that observes daylight saving time will skip a run during the spring-forward transition (when 2 AM does not exist) and potentially run twice during the fall-back transition. Use UTC for mission-critical jobs or handle DST explicitly.
Scheduling a task for the 31st of every month (0 0 31 * *) means it will only run in months that have 31 days (January, March, May, July, August, October, December). It will silently skip February, April, June, September, and November. For "end of month" scheduling, consider using the 28th or handling this in your script logic.
If a cron job takes longer than its interval to complete, the next invocation starts while the previous one is still running. This can lead to data corruption, deadlocks, and resource exhaustion. Use file locks (flock), Kubernetes concurrencyPolicy: Forbid, or application-level locking to prevent this.
Cron runs with a minimal environment. Commands that work in your terminal might fail in cron because the PATH is different. Always use absolute paths for commands or set the PATH explicitly in your crontab:
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * /usr/local/bin/python3 /home/user/scripts/task.py
A cron job that redirects stderr to /dev/null will silently discard all error messages, making debugging impossible. Always log errors to a file or monitoring system:
# Bad: errors disappear
0 2 * * * /scripts/task.sh > /dev/null 2>&1
# Good: errors are logged
0 2 * * * /scripts/task.sh >> /var/log/task.log 2>&1
Always verify your cron expression before deploying it. A small typo can completely change the schedule. Use a cron parser tool to confirm the next run times match your expectations.
Writing and interpreting cron expressions does not have to involve guesswork. Our free Cron Expression Parser & Generator makes it easy to work with cron syntax in two modes:
Paste any cron expression and instantly see:
Choose from 15 common presets (every 5 minutes, every hour, every weekday at 9 AM, etc.) to quickly see how different expressions work.
Enter values for each of the five fields and see the generated cron expression update in real time. The builder shows a live human-readable description and validates your input, catching errors before you deploy. Copy the expression to your clipboard with a single click.
Whether you are writing a crontab entry, configuring a Kubernetes CronJob, setting up a GitHub Actions schedule, or defining an AWS EventBridge rule, our tool helps you get the expression right the first time.
Stop guessing what your cron expression does. Use our free Cron Expression Parser to decode any cron schedule into plain English and see the next run times.
Try the Cron Parser Now