Cron Syntax Deep Dive
In this tutorial, you will learn about Cron Syntax Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.
Master cron syntax with advanced patterns: ranges, steps, lists, non-standard macros, month names, day names, and complex multi-field expressions for precise scheduling.
What You Learn
You will learn advanced cron syntax including complex ranges, step values with offsets, combining multiple operators in one field, named month and day abbreviations, and non-standard macros like @hourly.
Why It Matters
Basic cron syntax handles simple schedules, but real-world scheduling requires precision: every 90 minutes, every weekday at 8:15 AM except holidays, the last day of every month. Advanced syntax makes these possible.
Real-World Use
DodaTech uses complex cron: threat signature updates every 3 hours offset by 15 minutes (15 */3 * * *), report generation on the last weekday of each month (0 9 * * 1-5 with external script check), and staggered health checks every 7 minutes starting at minute 2 (2-59/7 * * * *).
Field Ranges and Steps
# Range: dash between values
# Run every hour during business hours (9 AM to 5 PM)
0 9-17 * * * /opt/scripts/business.py
# Step: slash after range or asterisk
# Every 15 minutes
*/15 * * * * /opt/scripts/quarter_hour.py
# Every 2 hours from 8 AM to 6 PM
0 8-18/2 * * * /opt/scripts/bi_hourly.sh
# Every 10 minutes between 9 AM and 5 PM
*/10 9-17 * * * /opt/scripts/ten_min.py
Combining Operators
# Comma-separated list of values
# Run at 8 AM, 12 PM, and 5 PM
0 8,12,17 * * * /opt/scripts/meals.sh
# Mixed: range and individual values
# Run every hour from 9 AM to 12 PM, and also at 5 PM
0 9-12,17 * * * /opt/scripts/mixed.py
# Complex: multiple lists
# Run on the 1st and 15th of Jan, Jun, Dec
0 0 1,15 1,6,12 * /opt/scripts/quarterly.sh
Step Values with Offset
# Step with offset: start at N, then every M
# Every 7 minutes starting at minute 2 (2, 9, 16, 23, ...)
2-59/7 * * * * /opt/scripts/staggered.sh
# Every 90 minutes (0:00, 1:30, 3:00, 4:30, ...)
# This requires two entries:
0 0-22/3 * * * /opt/scripts/90min.sh
30 1-23/3 * * * /opt/scripts/90min.sh
# Every 45 minutes (3 entries needed)
0 0-23/3 * * * /opt/scripts/45min.sh
45 0-23/3 * * * /opt/scripts/45min.sh
30 1-23/3 * * * /opt/scripts/45min.sh
Named Months and Days
# Cron accepts three-letter abbreviations for months and days
# Run at 9 AM on Monday through Friday
0 9 * * mon-fri /opt/scripts/weekdays.py
# Run on the first day of January, July, and December
0 0 1 jan,jul,dec * /opt/scripts/milestones.sh
# Run every Sunday at midnight
0 0 * * sun /opt/scripts/weekly_summary.py
# Avoid using named days/months in scripts for portability
# Numeric equivalents:
# sun=0, mon=1, tue=2, wed=3, thu=4, fri=5, sat=6
# jan=1, feb=2, ..., dec=12
Non-Standard Macros
# Some cron implementations support these shortcuts:
# @reboot: Run once at startup
@reboot /opt/scripts/startup.sh
# @yearly or @annually: Run once a year (0 0 1 1 *)
@yearly /opt/scripts/yearly_renewal.sh
# @monthly: Run once a month (0 0 1 * *)
@monthly /opt/scripts/monthly_cleanup.py
# @weekly: Run once a week (0 0 * * 0)
@weekly /opt/scripts/weekly_report.sh
# @daily or @midnight: Run once a day (0 0 * * *)
@daily /opt/scripts/daily_backup.sh
# @hourly: Run once an hour (0 * * * *)
@hourly /opt/scripts/hourly_health.py
Complex Real-World Examples
# First weekday of each month at 9 AM
# Cron cannot do "first weekday" directly; use script logic
0 9 1-7 * * /opt/scripts/first_weekday.sh
# Inside first_weekday.sh:
# if [ $(date +\%u) -le 5 ]; then ...; fi
# Last day of each month at 11:59 PM
59 23 28-31 * * /opt/scripts/last_day.sh
# Inside last_day.sh:
# if [ "$(date +\%d -d tomorrow)" = "01" ]; then ...; fi
# Every 30 seconds (cron minimum is 1 minute, use sleep)
* * * * * /opt/scripts/every_30s.sh
# Inside every_30s.sh:
# do_work; sleep 30; do_work
# Every 6 hours at minutes 0, 15, 30, 45
0,15,30,45 */6 * * * /opt/scripts/details.py
Validating Cron Syntax
#!/usr/bin/env python3
import sys
import re
def validate_cron(expression):
fields = expression.strip().split()
if len(fields) != 5:
return False, f"Expected 5 fields, got {len(fields)}"
patterns = [
(r'^(\*|\d+|\d+-\d+|\*/\d+|\d+-\d+/\d+)((,\d+|\d+-\d+|\*/\d+)*)$', 0, 59),
(r'^(\*|\d+|\d+-\d+|\*/\d+|\d+-\d+/\d+)((,\d+|\d+-\d+|\*/\d+)*)$', 0, 23),
(r'^(\*|\d+|\d+-\d+|\*/\d+|\d+-\d+/\d+)((,\d+|\d+-\d+|\*/\d+)*)$', 1, 31),
(r'^(\*|\d+|\d+-\d+|\*/\d+|\d+-\d+/\d+)((,\d+|\d+-\d+|\*/\d+)*)$', 1, 12),
(r'^(\*|\d+|\d+-\d+|\*/\d+|\d+-\d+/\d+)((,\d+|\d+-\d+|\*/\d+)*)$', 0, 7),
]
for i, (field, pattern, min_val, max_val) in enumerate(zip(fields, patterns)):
field_pattern, vmin, vmax = patterns[i]
if not re.match(field_pattern, field):
return False, f"Field {i+1} syntax invalid: {field}"
return True, "Valid cron expression"
if __name__ == '__main__':
expr = sys.argv[1] if len(sys.argv) > 1 else "*/15 * * * *"
valid, msg = validate_cron(expr)
print(f"{expr}: {msg}")
Expected output:
$ python3 validate_cron.py "*/15 9-17 * * 1-5"
*/15 9-17 * * 1-5: Valid cron expression
$ python3 validate_cron.py "0 25 * * *"
0 25 * * *: Field 2 syntax invalid: 25
Common Mistakes
1. Off-by-One in Day-Of-Week
Sunday is both 0 and 7. Monday is 1, Tuesday is 2, etc. Using 7 instead of 0 for Sunday causes unexpected behavior.
2. Overlapping Ranges
A range like 0 9-5 * * * is invalid because 9 > 5. Cron cannot reverse ranges. Use 0 9-23,0-5 * * * or two entries.
3. Month and Day-of-Week AND Logic
When both day-of-month and day-of-week are specified (not *), the job runs when EITHER matches. This is often unexpected.
4. Forgetting Percent Signs
The % character has special meaning in cron. It must be escaped with \% in commands.
5. Step Value Confusion
*/15 means every 15 units, but 0-30/15 means every 15 within the range 0-30, giving 0 and 30 only.
Practice Questions
1. What does 2-59/7 * * * * mean?
At minutes 2, 9, 16, 23, 30, 37, 44, and 51 of every hour.
2. How do you schedule a job every 90 minutes?
Two entries: 0 0-22/3 * * * and 30 1-23/3 * * *.
3. What is the difference between 0 0 * * 0 and 0 0 * * 7?
Both run at midnight on Sunday. Sunday is represented by both 0 and 7.
4. How do you run a job on the last day of every month?
Use 59 23 28-31 * * with a script that checks if tomorrow is the 1st.
Challenge
Write cron expressions for: every 7 minutes starting at minute 3, every 2 hours during business hours (8 AM to 6 PM), every Monday and Wednesday at 8:30 AM, the first day of each quarter (Jan 1, Apr 1, Jul 1, Oct 1), and every 45 minutes.
FAQ
Mini Project: Complex Scheduler
#!/usr/bin/env python3
import sys
from datetime import datetime
def parse_cron_expression(expr):
"""Parse and describe a cron expression in plain English."""
fields = expr.split()
if len(fields) != 5:
return "Invalid: requires 5 fields"
descriptions = {
'minute': ('minute', 0, 59),
'hour': ('hour', 0, 23),
'day': ('day of month', 1, 31),
'month': ('month', 1, 12),
'weekday': ('day of week', 0, 7),
}
result = []
for i, (field, (name, lo, hi)) in enumerate(zip(fields, descriptions.values())):
if field == '*':
result.append(f"every {name}")
elif field.startswith('*/'):
result.append(f"every {field[2:]} {name}s")
elif '-' in field and '/' in field:
rng, step = field.split('/')
result.append(f"every {step} {name}s from {rng}")
elif '-' in field:
result.append(f"{name}s {field}")
elif ',' in field:
result.append(f"{name}s {field}")
else:
result.append(f"{name} {field}")
return ', '.join(result)
if __name__ == '__main__':
examples = [
"*/15 9-17 * * 1-5",
"0 8,12,17 * * *",
"2-59/7 * * * *",
"0 0 1 1,4,7,10 *",
"30 8 * * 1-5",
]
for ex in examples:
print(f"{ex:20s} -> {parse_cron_expression(ex)}")
Expected output:
*/15 9-17 * * 1-5 -> every 15 minutes, hours 9-17, every day of month, every month, days of week 1-5
0 8,12,17 * * * -> minute 0, hours 8,12,17, every day of month, every month, every day of week
2-59/7 * * * * -> every 7 minutes from 2-59, every hour, every day of month, every month, every day of week
0 0 1 1,4,7,10 * -> minute 0, hour 0, day of month 1, months 1,4,7,10, every day of week
30 8 * * 1-5 -> minute 30, hour 8, every day of month, every month, days of week 1-5
What's Next
Now that you understand advanced cron syntax, explore special cron strings like @reboot and @daily, then learn about crontab file management.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro