Cron Jobs: Syntax and Practical Examples for Task Automation
Introduction
Cron is the time-based job scheduler in Unix-like operating systems, enabling system administrators to schedule commands or scripts to run automatically at specified times and intervals. Understanding cron is fundamental for any DevOps professional, as it powers countless automated tasks from backups and log rotation to monitoring checks and periodic maintenance.
This comprehensive guide covers everything from basic cron syntax to advanced scheduling patterns, real-world examples, troubleshooting, and best practices for production environments.
Understanding Cron Basics
What is Cron?
Cron is a daemon that runs continuously in the background, checking every minute for scheduled jobs. It reads schedules from cron tables (crontabs) and executes commands at their specified times.
Cron Components
- crond: The cron daemon that runs scheduled jobs
- crontab: Command to create, edit, list, and remove cron jobs
- cron.d: Directory for system cron job files
- /etc/crontab: System-wide crontab file
- User crontabs: Individual user cron schedules
Cron Syntax
Basic Cron Format
* * * * * command-to-execute
│ │ │ │ │
│ │ │ │ └─── Day of week (0-7, Sunday=0 or 7)
│ │ │ └───── Month (1-12)
│ │ └─────── Day of month (1-31)
│ └───────── Hour (0-23)
└─────────── Minute (0-59)
Special Characters
*(asterisk): Any value,(comma): Value list separator-(dash): Range of values/(slash): Step values@(at): Special strings
Special Strings
@reboot # Run once at startup
@yearly # Run once a year (0 0 1 1 *)
@annually # Same as @yearly
@monthly # Run once a month (0 0 1 * *)
@weekly # Run once a week (0 0 * * 0)
@daily # Run once a day (0 0 * * *)
@midnight # Same as @daily
@hourly # Run once an hour (0 * * * *)
Common Cron Patterns
Every Minute
* * * * * /path/to/script.sh
Every 5 Minutes
*/5 * * * * /path/to/script.sh
Every Hour
0 * * * * /path/to/script.sh
Every Day at Midnight
0 0 * * * /path/to/script.sh
Every Day at 3:30 AM
30 3 * * * /path/to/script.sh
Every Monday at 9 AM
0 9 * * 1 /path/to/script.sh
First Day of Every Month
0 0 1 * * /path/to/script.sh
Every Weekday at 6 PM
0 18 * * 1-5 /path/to/script.sh
Multiple Times Per Day
0 6,12,18 * * * /path/to/script.sh # At 6 AM, 12 PM, and 6 PM
Business Hours (9 AM to 5 PM, Weekdays)
0 9-17 * * 1-5 /path/to/script.sh
Every 15 Minutes During Business Hours
*/15 9-17 * * 1-5 /path/to/script.sh
Managing Cron Jobs
Crontab Commands
# List current user's cron jobs
crontab -l
# Edit current user's cron jobs
crontab -e
# Remove all cron jobs for current user
crontab -r
# List cron jobs for specific user (requires root)
sudo crontab -l -u username
# Edit cron jobs for specific user (requires root)
sudo crontab -e -u username
Creating Cron Jobs
# Open crontab editor
crontab -e
# Add your cron job (example: daily backup at 2 AM)
0 2 * * * /usr/local/bin/backup.sh
# Save and exit (vi: :wq, nano: Ctrl+X, Y, Enter)
System-Wide Cron Jobs
# Create system cron job in /etc/cron.d/
sudo nano /etc/cron.d/custom-job
# Content:
# Backup database every day at 1 AM
0 1 * * * root /opt/scripts/db-backup.sh
# Set proper permissions
sudo chmod 644 /etc/cron.d/custom-job
Practical Cron Examples
1. Daily Database Backup
# Cron entry
0 2 * * * /opt/scripts/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1
# Script: /opt/scripts/mysql-backup.sh
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/backup/mysql"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
mkdir -p "$BACKUP_DIR"
# Backup all databases
mysqldump --all-databases \
--single-transaction \
--routines \
--triggers | gzip > "${BACKUP_DIR}/all-databases-${TIMESTAMP}.sql.gz"
# Cleanup old backups
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup completed: $(date)"
2. Log Rotation
# Cron entry - Run daily at 11:55 PM
55 23 * * * /opt/scripts/rotate-logs.sh
# Script: /opt/scripts/rotate-logs.sh
#!/bin/bash
set -euo pipefail
LOG_DIRS=(
"/var/log/nginx"
"/var/log/application"
)
for dir in "${LOG_DIRS[@]}"; do
if [ -d "$dir" ]; then
# Compress logs older than 1 day
find "$dir" -name "*.log" -mtime +1 ! -name "*.gz" -exec gzip {} \;
# Delete logs older than 30 days
find "$dir" -name "*.gz" -mtime +30 -delete
fi
done
3. System Health Check
# Cron entry - Every 5 minutes
*/5 * * * * /opt/scripts/health-check.sh
# Script: /opt/scripts/health-check.sh
#!/bin/bash
set -euo pipefail
CPU_THRESHOLD=80
MEMORY_THRESHOLD=85
DISK_THRESHOLD=90
ALERT_EMAIL="[email protected]"
# Check CPU
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
if (( $(echo "$CPU > $CPU_THRESHOLD" | bc -l) )); then
echo "High CPU: ${CPU}%" | mail -s "CPU Alert" "$ALERT_EMAIL"
fi
# Check Memory
MEMORY=$(free | grep Mem | awk '{printf("%.0f", ($3/$2) * 100)}')
if (( MEMORY > MEMORY_THRESHOLD )); then
echo "High Memory: ${MEMORY}%" | mail -s "Memory Alert" "$ALERT_EMAIL"
fi
# Check Disk
DISK=$(df -h / | tail -1 | awk '{print $5}' | sed 's/%//')
if (( DISK > DISK_THRESHOLD )); then
echo "High Disk Usage: ${DISK}%" | mail -s "Disk Alert" "$ALERT_EMAIL"
fi
4. SSL Certificate Renewal
# Cron entry - Daily at 3 AM
0 3 * * * /opt/scripts/renew-ssl.sh
# Script: /opt/scripts/renew-ssl.sh
#!/bin/bash
set -euo pipefail
certbot renew --quiet --deploy-hook "systemctl reload nginx"
# Check certificate expiry
for domain in example.com api.example.com; do
EXPIRY=$(echo | openssl s_client -servername "$domain" \
-connect "$domain:443" 2>/dev/null | \
openssl x509 -noout -dates | grep notAfter | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
CURRENT_EPOCH=$(date +%s)
DAYS_REMAINING=$(( ($EXPIRY_EPOCH - $CURRENT_EPOCH) / 86400 ))
if (( DAYS_REMAINING < 30 )); then
echo "Certificate for $domain expires in $DAYS_REMAINING days" | \
mail -s "SSL Certificate Expiry Warning" [email protected]
fi
done
5. Website Monitoring
# Cron entry - Every 2 minutes
*/2 * * * * /opt/scripts/monitor-website.sh
# Script: /opt/scripts/monitor-website.sh
#!/bin/bash
set -euo pipefail
URLS=(
"https://example.com"
"https://api.example.com/health"
"https://admin.example.com"
)
LOG_FILE="/var/log/website-monitor.log"
for url in "${URLS[@]}"; do
if ! curl -f -s -o /dev/null -w "%{http_code}" --max-time 10 "$url" | grep -q "200"; then
echo "[$(date)] FAILED: $url" | tee -a "$LOG_FILE"
echo "Website $url is down" | mail -s "Website Down Alert" [email protected]
else
echo "[$(date)] OK: $url" >> "$LOG_FILE"
fi
done
6. Automated Security Updates
# Cron entry - Daily at 4 AM
0 4 * * * /opt/scripts/security-updates.sh
# Script: /opt/scripts/security-updates.sh
#!/bin/bash
set -euo pipefail
LOG_FILE="/var/log/security-updates.log"
{
echo "Starting security updates: $(date)"
# Ubuntu/Debian
if command -v apt-get &>/dev/null; then
apt-get update
apt-get upgrade -y --only-upgrade
apt-get autoremove -y
apt-get autoclean
fi
# CentOS/Rocky Linux
if command -v yum &>/dev/null; then
yum update -y --security
yum autoremove -y
fi
echo "Security updates completed: $(date)"
} | tee -a "$LOG_FILE"
# Check if reboot required
if [ -f /var/run/reboot-required ]; then
echo "Reboot required after updates" | mail -s "Reboot Required" [email protected]
fi
7. Disk Space Cleanup
# Cron entry - Weekly on Sunday at 1 AM
0 1 * * 0 /opt/scripts/cleanup-disk.sh
# Script: /opt/scripts/cleanup-disk.sh
#!/bin/bash
set -euo pipefail
# Remove old temporary files
find /tmp -type f -atime +7 -delete
find /var/tmp -type f -atime +7 -delete
# Clean package manager cache
apt-get clean 2>/dev/null || yum clean all 2>/dev/null
# Remove old log files
find /var/log -name "*.log.*" -mtime +30 -delete
find /var/log -name "*.gz" -mtime +30 -delete
# Clean Docker resources
docker system prune -af --volumes --filter "until=720h" 2>/dev/null || true
# Remove old kernels (Ubuntu/Debian)
if command -v apt-get &>/dev/null; then
apt-get autoremove --purge -y
fi
echo "Disk cleanup completed: $(date)"
8. Database Optimization
# Cron entry - Weekly on Saturday at 2 AM
0 2 * * 6 /opt/scripts/optimize-database.sh
# Script: /opt/scripts/optimize-database.sh
#!/bin/bash
set -euo pipefail
DB_USER="admin"
DB_PASS="password"
# MySQL/MariaDB optimization
mysql -u"$DB_USER" -p"$DB_PASS" -e "SHOW DATABASES;" | \
grep -Ev "Database|information_schema|performance_schema|mysql" | \
while read -r db; do
echo "Optimizing database: $db"
mysqlcheck -u"$DB_USER" -p"$DB_PASS" --optimize --skip-write-binlog "$db"
done
# PostgreSQL vacuum
sudo -u postgres psql -c "VACUUM ANALYZE;"
echo "Database optimization completed: $(date)"
Environment Variables in Cron
Cron runs with a minimal environment. Set required variables:
# Set PATH and other variables at the top of crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
[email protected]
# Then your cron jobs
0 2 * * * /opt/scripts/backup.sh
Output Handling
Redirect Output to Log File
# Append stdout and stderr to log file
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# Separate stdout and stderr
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>> /var/log/backup-error.log
# Discard all output
0 2 * * * /opt/scripts/backup.sh > /dev/null 2>&1
Email Output
# Set MAILTO at top of crontab
[email protected]
# Cron will email output
0 2 * * * /opt/scripts/backup.sh
# Or use mail command in script
0 2 * * * /opt/scripts/backup.sh | mail -s "Backup Report" [email protected]
Troubleshooting Cron Jobs
Check if Cron is Running
# Check cron service status
systemctl status cron # Debian/Ubuntu
systemctl status crond # CentOS/Rocky Linux
# Check if cron daemon is running
ps aux | grep cron
# Restart cron if needed
sudo systemctl restart cron
View Cron Logs
# View cron execution log
tail -f /var/log/syslog | grep CRON # Debian/Ubuntu
tail -f /var/log/cron # CentOS/Rocky Linux
# View recent cron activity
grep CRON /var/log/syslog | tail -20 # Debian/Ubuntu
tail -20 /var/log/cron # CentOS/Rocky Linux
Common Issues and Solutions
Issue: Cron job not running
# Verify cron syntax
# Use online tools: crontab.guru
# Check cron service
systemctl status cron
# Check user crontab
crontab -l
# Check system logs
grep CRON /var/log/syslog
Issue: Script works manually but not in cron
# Use absolute paths
# Bad: backup.sh
# Good: /opt/scripts/backup.sh
# Set PATH in crontab
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Use full paths in script
/usr/bin/mysql instead of mysql
Issue: Permissions denied
# Check script permissions
chmod +x /opt/scripts/backup.sh
# Check file ownership
chown user:user /opt/scripts/backup.sh
# Run as specific user
sudo crontab -e -u username
Testing Cron Jobs
# Test script manually
bash -x /opt/scripts/backup.sh
# Test with cron's environment
env -i HOME=$HOME LOGNAME=$LOGNAME PATH=/usr/bin:/bin SHELL=/bin/bash /bin/sh -c '/opt/scripts/backup.sh'
# Temporary frequent execution for testing
* * * * * /opt/scripts/test-script.sh
# After testing, remove or change to desired schedule
Best Practices
1. Use Absolute Paths
# Bad
0 2 * * * backup.sh
# Good
0 2 * * * /opt/scripts/backup.sh
2. Set Environment Variables
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
[email protected]
3. Implement Logging
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
4. Add Comments
# Daily database backup at 2 AM
0 2 * * * /opt/scripts/db-backup.sh
# Clean temporary files every Sunday
0 1 * * 0 /opt/scripts/cleanup.sh
5. Handle Errors
#!/bin/bash
set -euo pipefail
# Error handling in script
trap 'echo "Error occurred"' ERR
# Your script logic
6. Use Locking
Prevent multiple instances:
#!/bin/bash
LOCKFILE="/var/run/backup.lock"
# Check if already running
if [ -f "$LOCKFILE" ]; then
echo "Script already running"
exit 1
fi
# Create lock file
touch "$LOCKFILE"
# Cleanup on exit
trap "rm -f $LOCKFILE" EXIT
# Your script logic
7. Monitor Execution
# Log start and end times
#!/bin/bash
echo "Started: $(date)" >> /var/log/script.log
# Your script logic
echo "Completed: $(date)" >> /var/log/script.log
Security Considerations
File Permissions
# Crontab files should not be world-readable
chmod 600 ~/.crontab
# Script files should have appropriate permissions
chmod 750 /opt/scripts/backup.sh
# Restrict directory access
chmod 750 /opt/scripts
Sensitive Data
# Don't put passwords in crontab or scripts
# Use environment files or secret management
source /etc/backup.conf # Contains DB_PASS
# Or use .my.cnf for MySQL
mysqldump --defaults-file=/root/.my.cnf
User Isolation
# Run jobs as specific users
sudo crontab -e -u backup-user
# Use dedicated service accounts
# Don't run everything as root
Alternative: Anacron
For systems that aren't always on:
# Install anacron
sudo apt-get install anacron
# Edit /etc/anacrontab
1 5 daily-backup /opt/scripts/backup.sh
7 10 weekly-cleanup /opt/scripts/cleanup.sh
Conclusion
Cron is an essential tool for automating routine tasks in Linux environments. This guide covered everything from basic syntax to advanced patterns and production-ready examples. Master these concepts to effectively automate your infrastructure maintenance, monitoring, and operational tasks.
Key takeaways:
- Understand cron syntax and special characters
- Use absolute paths and set environment variables
- Implement comprehensive logging
- Handle errors gracefully
- Test thoroughly before production
- Monitor cron job execution
- Follow security best practices
- Document your scheduled tasks
Remember that while cron is powerful, modern systems may benefit from systemd timers for more advanced scheduling needs. Consider your requirements and choose the right tool for your automation needs.


