Creating Complex Cron Expressions
Beyond simple daily or weekly schedules, cron can express complex scheduling scenarios. However, cron has limitations, and some schedules require multiple entries or application logic.
Business Hours Scheduling
Standard Business Hours (9 AM to 5 PM, Weekdays)
0 9-17 * * 1-5
This runs every hour during business hours on weekdays:
- Hour 9-17 (9 AM to 5 PM)
- Monday through Friday
Every 2 Hours During Business Hours
0 9,11,13,15,17 * * 1-5
Alternatively:
0 */2 9-17 * * 1-5
However, this runs at 9, 11, 1, 3, 5 (not exactly 2-hour intervals). The step value is tricky with ranges.
Better approach for strict intervals:
0 9-17/2 * * 1-5
But interpret carefully: this gives 9, 11, 13, 15, 17.
Every 30 Minutes During Business Hours
*/30 9-17 * * 1-5
Runs at minutes 0 and 30 of every hour from 9 AM to 5 PM, weekdays.
Lunch Break Exclusion (9 AM to 12 PM, 1 PM to 5 PM)
This is complex in cron. You need separate entries:
# Morning block
0 9-12 * * 1-5 /job/morning.sh
# Afternoon block
0 13-17 * * 1-5 /job/afternoon.sh
Or use multiple cron entries that don't overlap.
Seasonal Schedules
Winter Schedule (December, January, February)
0 2 * 12,1,2 *
Runs at 2 AM every day in December, January, and February.
Quarterly Reports (1st of Mar, Jun, Sep, Dec)
0 9 1 3,6,9,12 *
First day of March, June, September, December at 9 AM.
Summer Only (June, July, August)
0 6 * 6-8 *
6 AM every day in summer months.
Advanced Time-Based Schedules
Every 6 Hours (Midnight, 6 AM, Noon, 6 PM)
0 */6 * * *
Every 3 Hours During 24-Hour Cycle
0 */3 * * *
Runs at midnight, 3 AM, 6 AM, 9 AM, noon, 3 PM, 6 PM, 9 PM.
Every 4 Hours During Business Hours
0 9,13,17 * * 1-5
9 AM, 1 PM, 5 PM on weekdays (not exactly 4 hours, but common business times).
Multiple Times Per Day
0 6,12,18 * * *
6 AM, noon, and 6 PM every day.
Complex Weekly Patterns
Monday, Wednesday, Friday (Every Other Day)
0 10 * * 1,3,5
10 AM on Mon, Wed, Fri.
Weekdays AND Saturday
0 9 * * 1-6
9 AM Monday through Saturday (excludes Sunday only).
Twice Daily on Specific Days
0 6,18 * * 1,4
6 AM and 6 PM on Monday and Thursday.
Last Friday of Each Month
Unfortunately, cron cannot express "last Friday" directly. Use multiple entries or application logic:
# Approximate: Run on Friday if day is between 22-31
0 10 22-31 * 5
# This might run multiple times in months where this overlaps
Better solution: Use multiple specific dates:
# Last Friday of each month - specific dates (requires manual update)
0 10 27 * 5 # Last Friday when month is 30 days
0 10 28 * 5 # Last Friday when month is 31 days
Or use application logic to determine the last Friday.
First Business Day of Month
First day that's a weekday (not weekends):
# First day of month, only if it's a weekday
0 9 1 * 1-5
# First Monday of month (combined with complex logic)
0 9 1-7 * 1
This is complex and better handled in application code.
Excluding Specific Dates
Cron cannot exclude dates (no NOT operator). Use application logic or multiple entries:
# Run daily except December 25
0 2 * * * if [ $(date +%m%d) != 1225 ]; then /backup.sh; fi
Complex Scenarios with Multiple Cron Entries
High-Priority Backup Schedule
# Hourly backup during business hours
0 9-17 * * 1-5 /backup/hourly.sh
# 4x daily backup outside business hours
0 0,6,18,22 * * * /backup/frequent.sh
# Daily full backup on weekends
0 2 * * 0,6 /backup/full.sh
Development vs Production Schedules
# Development schedule (more frequent)
ENVIRONMENT=dev
*/15 * * * 1-5 /dev/test-runner.sh
# Production schedule (less frequent)
ENVIRONMENT=prod
0 */4 * * * /prod/health-check.sh
Tiered Retention Schedule
# Hourly cleanup (every hour)
0 * * * * /cleanup/hourly.sh
# Daily cleanup (once per day)
0 3 * * * /cleanup/daily.sh
# Weekly cleanup (once per week)
0 3 * * 0 /cleanup/weekly.sh
# Monthly cleanup (once per month)
0 3 1 * * /cleanup/monthly.sh
Conditional Scheduling
Cron itself doesn't support conditions, but scripts can:
#!/bin/bash
# Run only if space is available
DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -lt 80 ]; then
/backup/backup.sh
else
echo "Disk usage too high: $DISK_USAGE%"
fi
Cron entry:
0 2 * * * /backup/conditional-backup.sh
Performance-Aware Scheduling
Stagger jobs to avoid overwhelming the system:
# Database maintenance staggered
# Backup
0 2 * * * /backup/full-backup.sh
# Optimization (starts after backup usually completes)
0 4 * * * /maintenance/optimize-db.sh
# Cleanup (after optimization)
0 6 * * * /maintenance/cleanup-logs.sh
Time Zone Aware Complex Schedules
For schedules across time zones, use application logic:
#!/bin/bash
# Run at 2 AM in specific timezone
TZ='America/New_York' HOUR=$(TZ=America/New_York date +%H)
if [ "$HOUR" -eq 2 ]; then
/backup/backup.sh
fi
Or use application schedulers (node-schedule, APScheduler) that natively support time zones.
Validating Complex Expressions
Before deploying complex schedules:
- Test with dry-run: Schedule for 2 minutes in the future and verify execution
- Use online validators: Tools like crontab.guru show when jobs run
- Check logs: Review
/var/log/cronto confirm actual execution - Document: Add comments explaining the schedule
# Backup schedule:
# - Hourly during business hours (9 AM - 5 PM, weekdays)
# - Daily at 10 PM every day
# - Full backup at 2 AM on weekends
0 9-17 * * 1-5 /backup/hourly.sh # Weekday hours
0 22 * * * /backup/daily.sh # Every night
0 2 * * 0,6 /backup/full.sh # Weekend full backup
Limitations and When to Use Application Logic
Cron cannot directly express:
- Last occurrence of month (e.g., "last Friday")
- Holidays
- Conditional execution
- Complex interval patterns
- Timezone-aware scheduling in older systems
For these cases, use:
- Conditional shells scripts for simple conditions
- Application schedulers (APScheduler, node-schedule) for complex logic
- Job queues (Celery, Bull) for distributed scheduling
- Managed services (AWS EventBridge, Google Cloud Scheduler) for advanced features
Real-World Examples
Ecommerce Website Schedule
# Hourly sales data aggregation (business hours)
0 9-21 * * * /reports/aggregate-sales.sh
# Daily email report
0 8 * * 1-5 /reports/email-report.sh
# Weekly inventory count
0 2 * * 1 /inventory/count.sh
# Monthly financial reconciliation
0 3 1 * * /finance/reconcile.sh
# Peak-season intensified monitoring
# (Memorial Day through Labor Day - approximated as June-August)
*/5 * * 6-8 * /monitoring/check-peak.sh
SaaS Application Schedule
# Billing cycles - daily attempts to charge
0 1 * * * /billing/charge-customers.sh
# Weekly trial reminder emails
0 10 * * 3 /email/send-trial-reminders.sh
# Monthly usage reports to customers
0 9 1 * * /reports/send-monthly-usage.sh
# Cleanup old sessions daily
0 3 * * * /maintenance/cleanup-sessions.sh
# Database optimization monthly
0 2 1 * * /maintenance/optimize-database.sh
Complex cron expressions handle many real-world scenarios, but always weigh the complexity against using application-level scheduling for very complex requirements. The goal is maintainability and reliability.
