Introduction to crontab
The crontab command is used to schedule tasks (cron jobs) to run automatically at specified times or intervals in Unix/Linux systems. It's an essential tool for automating repetitive tasks like backups, system maintenance, log rotation, and monitoring.
Key Concepts
- Cron: The daemon that executes scheduled commands
- Crontab: The file containing cron job definitions
- Cron Jobs: Individual scheduled commands
- Schedule Expression: Time specification for when to run jobs
- Environment: Cron jobs run with limited environment variables
1. Basic Crontab Commands
Managing Crontab Files
# List current user's crontab crontab -l # Edit current user's crontab crontab -e # Remove current user's crontab crontab -r # View another user's crontab (requires root) sudo crontab -u username -l # Edit another user's crontab (requires root) sudo crontab -u username -e # List with line numbers crontab -l | cat -n # Backup crontab crontab -l > my_crontab_backup.txt # Restore from backup crontab my_crontab_backup.txt # Check if crontab exists if crontab -l &>/dev/null; then echo "Crontab exists" else echo "No crontab for user" fi
Crontab File Format
# Each line has 6 fields: # minute hour day month day_of_week command # * * * * * command_to_execute # │ │ │ │ │ # │ │ │ │ └── day of week (0-7, 0 and 7 = Sunday) # │ │ │ └──── month (1-12) # │ │ └────── day of month (1-31) # │ └──────── hour (0-23) # └────────── minute (0-59) # Example: Run every day at 2:30 AM 30 2 * * * /home/user/backup.sh # Run every hour 0 * * * * /usr/bin/php /var/www/html/cron.php # Run every Monday at 5 PM 0 17 * * 1 /home/user/weekly_report.sh # Run on the 1st of every month at midnight 0 0 1 * * /home/user/monthly_cleanup.sh
2. Time Specification Syntax
Basic Time Values
# Numeric values # Minute: 0-59 # Hour: 0-23 # Day of month: 1-31 # Month: 1-12 # Day of week: 0-7 (0 and 7 = Sunday) # Examples 30 4 * * * /script.sh # Daily at 4:30 AM 0 22 * * * /script.sh # Daily at 10:00 PM 15 10 * * * /script.sh # Daily at 10:15 AM 0 0 * * * /script.sh # Daily at midnight
Using Ranges and Lists
# Ranges (using hyphen) 0 9-17 * * * /script.sh # Every hour from 9 AM to 5 PM 30 1-5 * * * /script.sh # At 1:30, 2:30, 3:30, 4:30, 5:30 # Lists (using commas) 15,30,45 * * * * /script.sh # At 15, 30, and 45 minutes past every hour 0 9,13,17 * * * /script.sh # At 9 AM, 1 PM, and 5 PM # Combined ranges and lists 0 9-17/2 * * * /script.sh # Every 2 hours from 9 AM to 5 PM 15,45 9-17 * * * /script.sh # At 9:15, 9:45, 10:15, 10:45, etc. # Multiple ranges 0 9-12,14-17 * * * /script.sh # Every hour from 9-12 and 14-17
Using Steps
# Steps (using /) */15 * * * * /script.sh # Every 15 minutes 0 */2 * * * /script.sh # Every 2 hours 0 0 */3 * * /script.sh # Every 3 days */10 9-17 * * * /script.sh # Every 10 minutes between 9 AM and 5 PM # Common step examples */5 * * * * /script.sh # Every 5 minutes */10 * * * * /script.sh # Every 10 minutes */30 * * * * /script.sh # Every 30 minutes 0 */6 * * * /script.sh # Every 6 hours 0 0 */2 * * /script.sh # Every 2 days
Special Keywords
# Special time specifications @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 * * * *) # Examples @reboot /home/user/startup.sh @daily /home/user/daily_backup.sh @weekly /home/user/weekly_report.sh @monthly /home/user/monthly_cleanup.sh @yearly /home/user/yearly_archive.sh # Note: These may not be available on all systems # For maximum compatibility, use numeric equivalents: # @daily -> 0 0 * * * # @hourly -> 0 * * * *
3. Practical Cron Job Examples
System Maintenance Jobs
# Edit crontab crontab -e # Daily system update (at 2 AM) 0 2 * * * /usr/bin/apt-get update && /usr/bin/apt-get upgrade -y >> /var/log/update.log 2>&1 # Weekly disk cleanup (Sunday at 3 AM) 0 3 * * 0 /home/user/scripts/cleanup.sh # Monthly log rotation (1st of month at 4 AM) 0 4 1 * * /usr/sbin/logrotate /etc/logrotate.conf # Check disk space every hour and alert if low 0 * * * * /home/user/scripts/check_disk.sh # Backup database daily at 1 AM 0 1 * * * /usr/bin/mysqldump -u root mydb > /backup/mydb_$(date +\%Y\%m\%d).sql # Remove old backups (older than 30 days) daily at 2 AM 0 2 * * * find /backup -type f -name "*.sql" -mtime +30 -delete
Backup Script with Crontab
#!/bin/bash
# backup.sh - Comprehensive backup script
# Configuration
BACKUP_DIR="/backup"
LOG_FILE="/var/log/backup.log"
DATABASES=("db1" "db2" "db3")
FILES=("/home/user/documents" "/home/user/pictures")
# Logging function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
# Create backup directory if not exists
mkdir -p "$BACKUP_DIR"
# Database backups
for db in "${DATABASES[@]}"; do
log "Starting database backup: $db"
if mysqldump -u backup "$db" | gzip > "$BACKUP_DIR/${db}_$(date +\%Y\%m\%d).sql.gz"; then
log "Database backup successful: $db"
else
log "Database backup failed: $db"
fi
done
# File backups
for dir in "${FILES[@]}"; do
name=$(basename "$dir")
log "Starting file backup: $name"
if tar -czf "$BACKUP_DIR/${name}_$(date +\%Y\%m\%d).tar.gz" "$dir" 2>/dev/null; then
log "File backup successful: $name"
else
log "File backup failed: $name"
fi
done
# Clean old backups (keep 30 days)
find "$BACKUP_DIR" -type f -name "*.gz" -mtime +30 -delete
log "Backup process completed"
Crontab Entries for the Script
# Database backup every 6 hours 0 */6 * * * /home/user/scripts/backup.sh # Daily backup at 1 AM 0 1 * * * /home/user/scripts/backup.sh >> /var/log/backup_cron.log 2>&1 # Weekly full backup (Sunday at 2 AM) 0 2 * * 0 /home/user/scripts/full_backup.sh # Monthly archive (1st of month at 3 AM) 0 3 1 * * /home/user/scripts/archive_monthly.sh
Monitoring Script
#!/bin/bash # monitor.sh - System monitoring script # Configuration THRESHOLD_CPU=80 THRESHOLD_MEM=90 THRESHOLD_DISK=85 ALERT_EMAIL="[email protected]" LOG_FILE="/var/log/monitor.log" # Get current timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') # Check CPU load load=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | xargs) cores=$(nproc) cpu_percent=$(echo "$load * 100 / $cores" | bc) if (( $(echo "$cpu_percent > $THRESHOLD_CPU" | bc -l) )); then message="High CPU usage: ${cpu_percent}% (threshold: ${THRESHOLD_CPU}%)" echo "$timestamp - $message" >> "$LOG_FILE" echo "$message" | mail -s "CPU Alert" "$ALERT_EMAIL" fi # Check memory usage mem_used=$(free | awk '/Mem:/ {print int($3/$2 * 100)}') if [ "$mem_used" -gt "$THRESHOLD_MEM" ]; then message="High memory usage: ${mem_used}% (threshold: ${THRESHOLD_MEM}%)" echo "$timestamp - $message" >> "$LOG_FILE" echo "$message" | mail -s "Memory Alert" "$ALERT_EMAIL" fi # Check disk usage df -h | grep -E '^/dev/' | while read line; do usage=$(echo "$line" | awk '{print $5}' | sed 's/%//') mount=$(echo "$line" | awk '{print $6}') if [ "$usage" -gt "$THRESHOLD_DISK" ]; then message="High disk usage on $mount: ${usage}%" echo "$timestamp - $message" >> "$LOG_FILE" echo "$message" | mail -s "Disk Alert" "$ALERT_EMAIL" fi done # Check if critical services are running services=("ssh" "cron" "nginx") for service in "${services[@]}"; do if ! systemctl is-active --quiet "$service"; then message="Service $service is not running" echo "$timestamp - $message" >> "$LOG_FILE" echo "$message" | mail -s "Service Alert" "$ALERT_EMAIL" # Attempt to restart systemctl start "$service" if [ $? -eq 0 ]; then echo "$timestamp - Restarted $service successfully" >> "$LOG_FILE" fi fi done
Crontab for Monitoring
# Monitor every 5 minutes */5 * * * * /home/user/scripts/monitor.sh # Check disk space every hour 0 * * * * /home/user/scripts/check_disk.sh # Monitor services every minute * * * * * /home/user/scripts/check_services.sh # Generate daily report at 11:55 PM 55 23 * * * /home/user/scripts/daily_report.sh
4. Environment and Output Handling
Setting Environment Variables
# Set environment variables in crontab SHELL=/bin/bash PATH=/usr/local/bin:/usr/bin:/bin [email protected] HOME=/home/user LOG_DIR=/var/log/myapp # Use variables in commands 0 2 * * * $HOME/scripts/backup.sh >> $LOG_DIR/backup.log 2>&1 # Example crontab with environment SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin [email protected] HOME=/home/user # Daily backup with logging 0 2 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1 # Weekly report 0 9 * * 1 /home/user/scripts/weekly_report.sh | mail -s "Weekly Report" [email protected]
Handling Output and Errors
# Redirect stdout and stderr to file 0 2 * * * /home/user/script.sh >> /var/log/script.log 2>&1 # Different files for stdout and stderr 0 2 * * * /home/user/script.sh >> /var/log/script.out 2>> /var/log/script.err # Suppress all output 0 2 * * * /home/user/script.sh > /dev/null 2>&1 # Send output by email (default if MAILTO is set) [email protected] 0 2 * * * /home/user/script.sh # Pipe output to another command 0 2 * * * /home/user/script.sh | logger -t myscript # Append timestamp to log 0 2 * * * echo "$(date): Starting backup" >> /var/log/backup.log && /home/user/backup.sh >> /var/log/backup.log 2>&1 # Rotate log files 0 0 * * * mv /var/log/script.log /var/log/script.log.$(date +\%Y\%m\%d) && touch /var/log/script.log
Using Logger for Syslog
# Log to syslog */5 * * * * /home/user/script.sh | logger -t myscript -p user.info # Log with different priorities 0 * * * * echo "Hourly check" | logger -t cron -p cron.info 0 2 * * * /home/user/backup.sh || logger -t cron -p cron.err "Backup failed" # View cron logs grep CRON /var/log/syslog journalctl -u cron -f
5. Advanced Crontab Techniques
Multiple Commands in One Line
# Run multiple commands (separate with && or ;) 0 2 * * * cd /home/user && ./backup.sh && echo "Backup complete" >> /var/log/backup.log # Run commands sequentially regardless of success 0 2 * * * command1; command2; command3 # Run only if previous succeeded 0 2 * * * command1 && command2 && command3 # Conditional execution with if 0 2 * * * if [ -f /tmp/lock ]; then exit; fi; /home/user/script.sh # Complex command chains 0 2 * * * /home/user/script.sh || /home/user/fallback.sh | mail -s "Alert" [email protected]
Using Conditional Execution
# Run only on weekdays
0 2 * * 1-5 /home/user/script.sh
# Run only on weekends
0 2 * * 0,6 /home/user/weekend_script.sh
# Run only if file exists
0 2 * * * [ -f /tmp/run ] && /home/user/script.sh
# Run only if not already running
0 2 * * * /usr/bin/flock -n /tmp/myscript.lock /home/user/script.sh
# Run only if load average is low
0 2 * * * [ $(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | cut -d. -f1) -lt 5 ] && /home/user/script.sh
# Check network connectivity before running
0 2 * * * ping -c 1 google.com &>/dev/null && /home/user/online_script.sh || /home/user/offline_script.sh
Preventing Overlapping Runs
# Using flock to prevent overlapping */5 * * * * /usr/bin/flock -n /tmp/myapp.lock /home/user/script.sh # Create PID file check 0 2 * * * /home/user/script_with_pid.sh # script_with_pid.sh contents: #!/bin/bash PIDFILE=/tmp/myscript.pid if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then echo "Script already running" exit 1 fi echo $$ > "$PIDFILE" # ... main script ... rm "$PIDFILE" # Using mkdir as atomic operation 0 2 * * * /home/user/script_with_lockdir.sh # script_with_lockdir.sh: #!/bin/bash LOCKDIR=/tmp/myscript.lock if mkdir "$LOCKDIR" 2>/dev/null; then trap "rm -rf '$LOCKDIR'" EXIT # ... main script ... else echo "Script already running" exit 1 fi
Dynamic Time Specifications
# Run at random minute to avoid contention
RANDOM_MINUTE=$((RANDOM % 60))
echo "$RANDOM_MINUTE 2 * * * /home/user/script.sh" | crontab -
# Run at calculated times
# Calculate next run time in script and schedule
#!/bin/bash
# dynamic_scheduler.sh
next_run=$(date -d "tomorrow 2am" +%M %H %d %m %w)
echo "$next_run /home/user/script.sh" | crontab -
# Use system load to determine if should run
0 2 * * * /home/user/conditional_runner.sh
# conditional_runner.sh:
#!/bin/bash
load=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1)
if (( $(echo "$load < 2" | bc -l) )); then
/home/user/main_script.sh
else
echo "Load too high, skipping run" >> /var/log/skipped.log
fi
6. Managing Multiple Crontabs
User-Specific Crontabs
# Each user has their own crontab crontab -u user1 -l crontab -u user2 -e crontab -u user3 -r # System-wide crontab (/etc/crontab) sudo cat /etc/crontab # Format for /etc/crontab includes user field: # minute hour day month day-of-week user command 0 2 * * * root /usr/bin/backup.sh # Drop-in directories /etc/cron.d/ # System cron files /etc/cron.hourly/ # Scripts run hourly /etc/cron.daily/ # Scripts run daily /etc/cron.weekly/ # Scripts run weekly /etc/cron.monthly/ # Scripts run monthly
System Cron Directories
# Place scripts in these directories to run at intervals # Hourly scripts sudo cp myscript.sh /etc/cron.hourly/ sudo chmod +x /etc/cron.hourly/myscript.sh # Daily scripts sudo cp backup.sh /etc/cron.daily/ sudo chmod +x /etc/cron.daily/backup.sh # Weekly scripts sudo cp report.sh /etc/cron.weekly/ sudo chmod +x /etc/cron.weekly/report.sh # Monthly scripts sudo cp cleanup.sh /etc/cron.monthly/ sudo chmod +x /etc/cron.monthly/cleanup.sh # Note: Scripts in these directories should: # 1. Not require terminal input # 2. Handle errors gracefully # 3. Log output appropriately # 4. Be idempotent
Crontab Management Script
#!/bin/bash
# manage_crontab.sh - Manage multiple crontab configurations
CRONTAB_DIR="/home/user/crontabs"
mkdir -p "$CRONTAB_DIR"
# Function to backup current crontab
backup_crontab() {
local user="${1:-$USER}"
local backup_file="$CRONTAB_DIR/${user}_crontab_$(date +%Y%m%d_%H%M%S).txt"
if crontab -u "$user" -l &>/dev/null; then
crontab -u "$user" -l > "$backup_file"
echo "Backed up crontab for $user to $backup_file"
else
echo "No crontab for $user"
fi
}
# Function to load crontab from file
load_crontab() {
local file="$1"
local user="${2:-$USER}"
if [ ! -f "$file" ]; then
echo "File not found: $file"
return 1
fi
crontab -u "$user" "$file"
echo "Loaded crontab for $user from $file"
}
# Function to list all user crontabs
list_all_crontabs() {
echo "=== User Crontabs ==="
for user in $(cut -d: -f1 /etc/passwd); do
if crontab -u "$user" -l &>/dev/null; then
echo "--- $user ---"
crontab -u "$user" -l | head -5
echo "..."
fi
done
}
# Function to enable/disable cron jobs
toggle_job() {
local pattern="$1"
local action="${2:-disable}" # disable or enable
crontab -l | while read line; do
if [[ "$line" =~ $pattern && "$action" == "disable" ]]; then
echo "# DISABLED: $line"
elif [[ "$line" =~ ^#.*DISABLED.*$pattern && "$action" == "enable" ]]; then
echo "${line#*DISABLED: }"
else
echo "$line"
fi
done | crontab -
echo "Jobs matching '$pattern' ${action}d"
}
# Function to validate crontab syntax
validate_crontab() {
local file="${1:-/dev/stdin}"
if crontab -l 2>/dev/null | crontab - 2>&1; then
echo "✓ Crontab syntax is valid"
else
echo "✗ Crontab syntax error"
fi
}
# Function to show next run times
next_runs() {
local count="${1:-5}"
crontab -l | grep -v '^#' | while read line; do
if [ -n "$line" ]; then
echo "Next run for: $line"
echo " $(echo "$line" | awk '{print $1" "$2" "$3" "$4" "$5}')"
fi
done
}
# Menu
case "${1:-}" in
backup)
backup_crontab "${2:-$USER}"
;;
load)
load_crontab "$2" "${3:-$USER}"
;;
list)
list_all_crontabs
;;
toggle)
toggle_job "$2" "${3:-disable}"
;;
validate)
validate_crontab
;;
next)
next_runs "${2:-5}"
;;
*)
echo "Usage: $0 {backup|load|list|toggle|validate|next}"
;;
esac
7. Cron Job Best Practices
Script Best Practices
#!/bin/bash
# Example of a well-behaved cron script
# 1. Use full paths
BACKUP_SCRIPT="/home/user/scripts/backup.sh"
LOG_FILE="/var/log/backup.log"
# 2. Set environment
export PATH=/usr/local/bin:/usr/bin:/bin
export HOME=/home/user
# 3. Use locking to prevent overlap
LOCKFILE="/tmp/$(basename "$0").lock"
if [ -f "$LOCKFILE" ] && kill -0 $(cat "$LOCKFILE") 2>/dev/null; then
echo "$(date): Script already running" >> "$LOG_FILE"
exit 1
fi
echo $$ > "$LOCKFILE"
trap "rm -f '$LOCKFILE'" EXIT
# 4. Handle errors gracefully
error_handler() {
echo "$(date): Error on line $1" >> "$LOG_FILE"
exit 1
}
trap 'error_handler $LINENO' ERR
# 5. Log everything
log() {
echo "$(date): $1" >> "$LOG_FILE"
}
# 6. Check prerequisites
check_prereqs() {
for cmd in mysqldump gzip tar; do
if ! command -v "$cmd" &>/dev/null; then
log "ERROR: $cmd not found"
exit 1
fi
done
}
# 7. Main script logic
main() {
log "Starting backup"
# Your backup logic here
if /usr/bin/mysqldump -u root mydb | gzip > "/backup/mydb_$(date +%Y%m%d).sql.gz"; then
log "Backup successful"
else
log "Backup failed"
exit 1
fi
log "Backup completed"
}
# 8. Run main function
check_prereqs
main
Logging and Monitoring
# Create a logging wrapper for cron jobs
#!/bin/bash
# cron_wrapper.sh - Wrapper script for cron jobs
JOB_NAME=$(basename "$1")
LOG_DIR="/var/log/cron"
LOG_FILE="$LOG_DIR/${JOB_NAME}_$(date +%Y%m%d).log"
PID_FILE="/tmp/${JOB_NAME}.pid"
# Create log directory
mkdir -p "$LOG_DIR"
# Log function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
# Check if already running
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
log "Job already running with PID $(cat "$PID_FILE")"
exit 1
fi
echo $$ > "$PID_FILE"
trap "rm -f '$PID_FILE'" EXIT
log "Starting job: $@"
# Execute the actual job
"$@" >> "$LOG_FILE" 2>&1
JOB_EXIT=$?
if [ $JOB_EXIT -eq 0 ]; then
log "Job completed successfully"
else
log "Job failed with exit code $JOB_EXIT"
# Send alert on failure
if [ -n "$ALERT_EMAIL" ]; then
tail -50 "$LOG_FILE" | mail -s "Cron job failed: $JOB_NAME" "$ALERT_EMAIL"
fi
fi
exit $JOB_EXIT
Error Handling and Notifications
#!/bin/bash
# error_handler.sh - Robust error handling for cron jobs
# Configuration
SCRIPT_NAME=$(basename "$0")
LOG_FILE="/var/log/${SCRIPT_NAME%.sh}.log"
ALERT_EMAIL="[email protected]"
MAX_RETRIES=3
RETRY_DELAY=60
# Logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
echo "$1"
}
# Error handling
handle_error() {
local exit_code=$1
local error_msg=$2
log "ERROR: $error_msg (exit code: $exit_code)"
# Send alert email
{
echo "Cron job failed: $SCRIPT_NAME"
echo "Exit code: $exit_code"
echo "Error: $error_msg"
echo "Time: $(date)"
echo ""
echo "Last 50 lines of log:"
tail -50 "$LOG_FILE"
} | mail -s "Cron Alert: $SCRIPT_NAME" "$ALERT_EMAIL"
# Optional: notify via other channels
# curl -X POST https://api.telegram.org/bot$TOKEN/sendMessage -d "chat_id=$CHAT_ID&text=$error_msg"
}
# Retry function
retry() {
local n=1
local cmd="$@"
until eval "$cmd"; do
log "Command failed (attempt $n/$MAX_RETRIES): $cmd"
if [ $n -lt $MAX_RETRIES ]; then
log "Retrying in $RETRY_DELAY seconds..."
sleep $RETRY_DELAY
((n++))
else
handle_error $? "Command failed after $MAX_RETRIES attempts: $cmd"
return 1
fi
done
log "Command succeeded: $cmd"
return 0
}
# Main execution with error handling
main() {
log "Starting $SCRIPT_NAME"
# Check dependencies
for cmd in curl wget mysql; do
if ! command -v "$cmd" &>/dev/null; then
handle_error 1 "Required command not found: $cmd"
exit 1
fi
done
# Example: retry a database backup
if retry mysqldump -u backup mydb > /tmp/backup.sql; then
log "Database backup successful"
else
log "Database backup failed after retries"
exit 1
fi
# Example: upload with retry
if retry curl -T /tmp/backup.sql ftp://backup-server/; then
log "Upload successful"
else
log "Upload failed after retries"
exit 1
fi
log "$SCRIPT_NAME completed successfully"
}
# Run main with trap
trap 'handle_error $? "Script interrupted"' INT TERM
main
8. Testing and Debugging Cron Jobs
Testing Cron Syntax
# Check crontab syntax crontab -l | crontab - 2>&1 # Test run a cron job manually /home/user/scripts/test.sh # Simulate cron environment env -i SHELL=/bin/bash PATH=/usr/local/bin:/usr/bin:/bin HOME=/home/user /home/user/script.sh # Test with specific time # Use date command to set time for testing sudo date -s "2024-01-01 02:00:00" # Run job # Set date back sudo ntpdate -u pool.ntp.org # Use run-parts to test system cron directories run-parts --test /etc/cron.daily
Debugging Tools
# Check cron logs grep CRON /var/log/syslog tail -f /var/log/syslog | grep CRON # Check if cron service is running systemctl status cron service cron status ps aux | grep cron # Check mail for cron output mail # View mail for specific user sudo cat /var/mail/$USER # Test individual commands # Run with full logging /home/user/script.sh >> /tmp/debug.log 2>&1 # Use logger for debugging echo "Debug: Starting job" | logger -t cron-debug # Check environment variables env | sort > /tmp/env_before.txt # Run job env | sort > /tmp/env_after.txt diff /tmp/env_before.txt /tmp/env_after.txt
Debugging Script
#!/bin/bash
# debug_cron.sh - Debug cron job issues
CRON_LOG="/var/log/syslog"
USER=${1:-$USER}
echo "=== Cron Debug Tool ==="
echo "User: $USER"
echo
# Check if cron is running
echo "1. Cron service status:"
if systemctl is-active --quiet cron 2>/dev/null; then
echo " ✓ Cron is running"
else
echo " ✗ Cron is NOT running"
fi
echo
# Check user's crontab
echo "2. User crontab:"
if crontab -l &>/dev/null; then
crontab -l | cat -n
else
echo " No crontab for user"
fi
echo
# Check recent cron activity
echo "3. Recent cron activity (last 10 entries):"
sudo grep "CRON" "$CRON_LOG" 2>/dev/null | tail -10
echo
# Check for errors
echo "4. Recent cron errors:"
sudo grep -i "error\|fail" "$CRON_LOG" 2>/dev/null | grep CRON | tail -5
echo
# Check mail
echo "5. Pending mail:"
if [ -f "/var/mail/$USER" ]; then
ls -l "/var/mail/$USER"
echo " Mail size: $(du -h /var/mail/$USER | cut -f1)"
else
echo " No mail file"
fi
echo
# Test environment
echo "6. Cron environment simulation:"
env -i SHELL=/bin/bash PATH=/usr/local/bin:/usr/bin:/bin HOME="/home/$USER" bash -c 'echo " PATH=$PATH"'
echo
# Test execute permissions
echo "7. Checking script permissions:"
crontab -l | grep -v '^#' | grep -E '^[^ ]' | while read line; do
cmd=$(echo "$line" | awk '{for(i=6;i<=NF;i++) printf "%s ", $i}')
script=$(echo "$cmd" | awk '{print $1}')
if [ -f "$script" ]; then
if [ -x "$script" ]; then
echo " ✓ $script is executable"
else
echo " ✗ $script is NOT executable"
fi
else
echo " ? $script not found"
fi
done
9. Security Considerations
Securing Crontab Access
# Restrict crontab access to specific users # /etc/cron.allow - users allowed to use crontab # /etc/cron.deny - users denied from using crontab # Example: Only allow root and backup user echo "root" >> /etc/cron.allow echo "backup" >> /etc/cron.allow echo "ALL" >> /etc/cron.deny # Deny all others # Secure permissions chmod 600 /etc/cron.allow chmod 600 /etc/cron.deny chmod 600 /var/spool/cron/* # Protect crontab files # Run cron jobs with minimal privileges # Create dedicated user for cron jobs sudo useradd -r -s /bin/false cronjobs sudo -u cronjobs crontab -e # Use sudo for specific tasks # In crontab: 0 2 * * * sudo -u backup /usr/local/bin/backup.sh
Safe Practices
# 1. Don't run as root unless necessary
# Bad
0 2 * * * /home/user/script.sh
# Good
0 2 * * * sudo -u user /home/user/script.sh
# 2. Use absolute paths
# Bad
0 2 * * * backup.sh
# Good
0 2 * * * /home/user/scripts/backup.sh
# 3. Validate input in scripts
#!/bin/bash
# Safe script example
if [ -z "$1" ]; then
echo "Usage: $0 <argument>"
exit 1
fi
# 4. Avoid using environment variables that could be manipulated
# Bad
0 2 * * * $HOME/script.sh
# Good
0 2 * * * /home/user/script.sh
# 5. Use locking to prevent resource exhaustion
0 2 * * * /usr/bin/flock -n /tmp/myscript.lock /home/user/script.sh
# 6. Log all actions
0 2 * * * /home/user/script.sh >> /var/log/script.log 2>&1
# 7. Set umask for secure file creation
0 2 * * * umask 077 && /home/user/script.sh
# 8. Monitor for unusual cron jobs
find /var/spool/cron -type f -exec ls -l {} \;
grep -r "curl\|wget\|nc\|bash -i" /var/spool/cron/
10. Advanced Examples
Complex Scheduling Patterns
# Run on specific days of month # 1st and 15th at 2 AM 0 2 1,15 * * /home/user/script.sh # Run on last day of month (using date command) 55 23 28-31 * * [ $(date -d tomorrow +%d) -eq 1 ] && /home/user/script.sh # Run every weekday except holidays 0 9 * * 1-5 [ ! -f /etc/holidays/$(date +%Y%m%d) ] && /home/user/script.sh # Run at specific times with offset # Every 2 hours, starting at 1 AM 1 */2 * * * /home/user/script.sh # Run every 3 hours between 9 AM and 6 PM 0 9-18/3 * * * /home/user/script.sh # Run twice daily at random minutes RANDOM1=$((RANDOM % 60)) RANDOM2=$((RANDOM % 60)) echo "$RANDOM1 9 * * * /home/user/morning.sh" | crontab - echo "$RANDOM2 17 * * * /home/user/evening.sh" | crontab -
Weather-Aware Scheduling
#!/bin/bash
# weather_aware.sh - Run based on weather conditions
# Get weather data (example using wttr.in)
weather=$(curl -s "wttr.in?format=%t+%w+%p")
temp=$(echo "$weather" | awk '{print $1}' | sed 's/[^0-9-]//g')
wind=$(echo "$weather" | awk '{print $2}' | sed 's/[^0-9]//g')
precip=$(echo "$weather" | awk '{print $3}' | sed 's/[^0-9]//g')
LOG_FILE="/var/log/weather_job.log"
log() {
echo "$(date): $1" >> "$LOG_FILE"
}
# Decision logic
if [ "$precip" -gt 0 ] && [ "$precip" -lt 30 ]; then
log "Light rain detected, running job"
/home/user/rain_job.sh
elif [ "$wind" -gt 20 ]; then
log "High winds, delaying job"
# Reschedule for later
echo "$((RANDOM % 60)) $((RANDOM % 23)) * * * /home/user/weather_aware.sh" | crontab -
elif [ "$temp" -lt 0 ]; then
log "Freezing temperature, running cold-weather job"
/home/user/cold_job.sh
else
log "Normal conditions, running regular job"
/home/user/regular_job.sh
fi
Load-Aware Scheduling
#!/bin/bash
# load_aware.sh - Run based on system load
LOAD_THRESHOLD=2.0
MAX_RETRIES=5
RETRY_DELAY=300 # 5 minutes
get_load() {
uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | xargs
}
run_with_load_check() {
local cmd="$@"
local attempt=1
while [ $attempt -le $MAX_RETRIES ]; do
load=$(get_load)
if (( $(echo "$load < $LOAD_THRESHOLD" | bc -l) )); then
echo "$(date): Load $load below threshold, running command"
eval "$cmd"
return $?
else
echo "$(date): Load $load above threshold, attempt $attempt/$MAX_RETRIES"
sleep $RETRY_DELAY
((attempt++))
fi
done
echo "$(date): Maximum retries reached, command skipped"
return 1
}
# Usage
run_with_load_check /home/user/heavy_job.sh
Chained Jobs with Dependencies
#!/bin/bash
# job_chain.sh - Chain multiple jobs with dependencies
JOB_DIR="/home/user/jobs"
LOG_DIR="/var/log/job_chain"
LOCK_DIR="/tmp/job_chain.lock"
mkdir -p "$LOG_DIR" "$LOCK_DIR"
run_job() {
local job="$1"
local log="$LOG_DIR/$(basename "$job")_$(date +%Y%m%d).log"
echo "$(date): Starting $job" >> "$log"
if bash "$job" >> "$log" 2>&1; then
echo "$(date): Job $job completed" >> "$log"
touch "$LOCK_DIR/$(basename "$job").done"
return 0
else
echo "$(date): Job $job failed" >> "$log"
return 1
fi
}
# Define job dependencies
jobs=(
"01_prepare.sh"
"02_process.sh:01_prepare.sh.done"
"03_analyze.sh:02_process.sh.done"
"04_cleanup.sh:03_analyze.sh.done"
)
for job_spec in "${jobs[@]}"; do
job=$(echo "$job_spec" | cut -d: -f1)
dep=$(echo "$job_spec" | cut -d: -f2-)
# Check dependency
if [ -n "$dep" ] && [ ! -f "$LOCK_DIR/$dep" ]; then
echo "Skipping $job, dependency $dep not satisfied"
continue
fi
# Run job with exclusive lock
(
flock -n 9 || exit 1
run_job "$JOB_DIR/$job"
) 9>"$LOCK_DIR/$(basename "$job").lock"
if [ $? -ne 0 ]; then
echo "Job chain failed at $job"
exit 1
fi
done
echo "Job chain completed successfully"
11. Monitoring and Alerting
Cron Job Monitoring System
#!/bin/bash # cron_monitor.sh - Monitor cron jobs and send alerts CONFIG_FILE="/etc/cron_monitor.conf" ALERT_EMAIL="[email protected]" CHECK_INTERVAL=3600 # 1 hour # Load configuration [ -f "$CONFIG_FILE" ] && source "$CONFIG_FILE" # Check if a job is running too long check_job_duration() { local job_pattern="$1" local max_minutes="$2" pgrep -f "$job_pattern" | while read pid; do start_time=$(ps -o lstart= -p $pid) start_seconds=$(date -d "$start_time" +%s) now_seconds=$(date +%s) duration=$(( (now_seconds - start_seconds) / 60 )) if [ $duration -gt $max_minutes ]; then echo "Job $job_pattern (PID $pid) running for ${duration} minutes (max: $max_minutes)" return 1 fi done return 0 } # Check if job ran within expected time window check_job_timing() { local job_name="$1" local expected_time="$2" local log_file="$3" last_run=$(grep "$job_name" "$log_file" | tail -1 | cut -d' ' -f1-2) last_seconds=$(date -d "$last_run" +%s 2>/dev/null) now_seconds=$(date +%s) if [ -n "$last_seconds" ]; then diff_hours=$(( (now_seconds - last_seconds) / 3600 )) if [ $diff_hours -gt $expected_time ]; then echo "Job $job_name last ran $diff_hours hours ago (expected within $expected_time hours)" return 1 fi else echo "Job $job_name never ran" return 1 fi return 0 } # Send alert send_alert() { local subject="$1" local message="$2" echo "$message" | mail -s "$subject" "$ALERT_EMAIL" # Optional: Send to Slack if [ -n "$SLACK_WEBHOOK" ]; then curl -X POST -H 'Content-type: application/json' \ --data "{\"text\":\"$subject\n$message\"}" \ "$SLACK_WEBHOOK" fi # Optional: Send to Telegram if [ -n "$TELEGRAM_BOT" ] && [ -n "$TELEGRAM_CHAT" ]; then curl -s "https://api.telegram.org/bot$TELEGRAM_BOT/sendMessage" \ -d "chat_id=$TELEGRAM_CHAT" \ -d "text=$subject\n$message" fi } # Main monitoring loop while true; do alerts="" # Check each configured job while IFS= read -r line; do [[ "$line" =~ ^#.*$ || -z "$line" ]] && continue job_pattern=$(echo "$line" | cut -d'|' -f1) max_duration=$(echo "$line" | cut -d'|' -f2) expected_interval=$(echo "$line" | cut -d'|' -f3) log_file=$(echo "$line" | cut -d'|' -f4) # Check duration if ! check_job_duration "$job_pattern" "$max_duration"; then alerts+="Job $job_pattern running too long\n" fi # Check timing if [ -n "$expected_interval" ] && [ -n "$log_file" ]; then if ! check_job_timing "$job_pattern" "$expected_interval" "$log_file"; then alerts+="Job $job_pattern not running on schedule\n" fi fi done < "$CONFIG_FILE" # Send alerts if any if [ -n "$alerts" ]; then send_alert "Cron Monitor Alert" "$alerts" fi sleep $CHECK_INTERVAL done
Configuration File Example
# /etc/cron_monitor.conf # Format: job_pattern|max_duration_minutes|expected_interval_hours|log_file # Database backup - should run within 30 minutes, expected every 24 hours mysqldump|30|24|/var/log/backup.log # Log rotation - should complete in 10 minutes, expected every 24 hours logrotate|10|24|/var/log/logrotate.log # System update - can take up to 60 minutes, expected weekly (168 hours) apt-get|60|168|/var/log/update.log # Monitoring job - should run continuously monitor.sh|60||
12. Crontab Cheat Sheet
Quick Reference
# Field Format # ┌──────── minute (0-59) # │ ┌────── hour (0-23) # │ │ ┌──── day of month (1-31) # │ │ │ ┌── month (1-12) # │ │ │ │ ┌ day of week (0-6 or 0-7, 0/7 = Sunday) # │ │ │ │ │ # * * * * * command # Common Examples * * * * * command # Every minute */5 * * * * command # Every 5 minutes 0 * * * * command # Every hour 0 0 * * * command # Daily at midnight 0 2 * * * command # Daily at 2 AM 0 0 * * 0 command # Weekly on Sunday 0 0 1 * * command # Monthly on 1st 0 0 1 1 * command # Yearly on Jan 1st # Ranges and Lists 0 9-17 * * * command # Every hour 9 AM - 5 PM 0 9,13,17 * * * command # At 9 AM, 1 PM, 5 PM 15,30,45 * * * * command # At 15, 30, 45 minutes 0 */2 * * * command # Every 2 hours # Special Keywords @reboot # At startup @yearly # 0 0 1 1 * @monthly # 0 0 1 * * @weekly # 0 0 * * 0 @daily # 0 0 * * * @midnight # 0 0 * * * @hourly # 0 * * * * # Common crontab commands crontab -l # List current crontab crontab -e # Edit current crontab crontab -r # Remove current crontab crontab -u user -l # List user's crontab crontab file.txt # Load crontab from file
Quick Setup Examples
# Daily backup at 2 AM with logging echo "0 2 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1" | crontab - # Multiple jobs cat << EOF | crontab - # Database backup at 1 AM 0 1 * * * /home/user/db_backup.sh # Log rotation at 2 AM 0 2 * * * /home/user/rotate_logs.sh # Weekly report on Monday at 9 AM 0 9 * * 1 /home/user/weekly_report.sh # Monitoring every 5 minutes */5 * * * * /home/user/monitor.sh EOF # Conditional job 0 2 * * * [ $(date +\%u) -le 5 ] && /home/user/weekday_job.sh # Job with flock to prevent overlap */5 * * * * /usr/bin/flock -n /tmp/mylock /home/user/script.sh
Conclusion
Crontab is an essential tool for automating tasks in Unix/Linux systems:
Key Takeaways
- Simple Syntax: Five time fields followed by command
- Flexible Scheduling: Ranges, lists, steps, and special keywords
- User-Specific: Each user can have their own crontab
- System-Wide: /etc/crontab and cron directories for system tasks
- Environment: Cron provides limited environment, set explicitly
- Logging: Always redirect output for debugging
- Security: Use least privilege, validate inputs, prevent overlaps
Best Practices Summary
| Area | Best Practice |
|---|---|
| Paths | Use absolute paths for all commands and files |
| Logging | Redirect stdout/stderr to log files |
| Environment | Set PATH and other variables explicitly |
| Testing | Test commands manually before adding to crontab |
| Error Handling | Check exit codes and handle failures |
| Locking | Prevent overlapping runs with flock |
| Permissions | Run jobs with minimal privileges |
| Monitoring | Monitor cron logs and job execution |
| Documentation | Comment complex crontab entries |
| Backup | Regularly backup crontab configurations |
Common Issues and Solutions
| Issue | Solution |
|---|---|
| Job not running | Check cron service, syntax, permissions |
| PATH issues | Set full PATH in crontab |
| Script fails | Test manually with same environment |
| Overlapping runs | Use flock or PID files |
| No output | Check MAILTO or redirect to file |
| Wrong time | Verify system timezone |
| Permission denied | Check script executable bits |
| Command not found | Use full paths |
Mastering crontab is essential for system administration and automation. Start with simple schedules and gradually incorporate more complex patterns as needed.
Complete Bash Exercises with Solutions
Practice Bash scripting with solved exercises to improve your command-line and scripting skills.
Link: https://macronepal.com/complete-bash-exercises-with-solutions/
Complete Guide to Bash Crontab Command
Learn how to schedule and automate tasks efficiently using the Bash crontab command.
Link: https://macronepal.com/complete-guide-to-bash-crontab-command-schedule-tasks/
Complete Guide to Bash Arrays
Understand how to create, manage, and use arrays in Bash scripting.
Link: https://macronepal.com/complete-guide-to-bash-arrays/
Bash Quiz: Test Your Knowledge
Test your Bash scripting knowledge with quizzes and practical questions.
Link: https://macronepal.com/bash-quiz-test-your-knowledge/
Complete Guide to Bash Functions
Learn how to write reusable Bash functions for cleaner and efficient scripts.
Link: https://macronepal.com/complete-guide-to-bash-functions/
Complete Guide to Bash Loops
Master loops in Bash for repetitive tasks and automation.
Link: https://macronepal.com/complete-guide-to-bash-loops/
Complete Guide to Bash If-Else Statements
Learn decision-making in Bash using conditional statements.
Link: https://macronepal.com/complete-guide-to-bash-ifelse-statements/
Complete Guide to Bash Operators
Explore arithmetic, logical, and comparison operators in Bash.
Link: https://macronepal.com/complete-guide-to-bash-operators/
Complete Guide to Bash Data Types
Understand variables and different data types used in Bash scripting.
Link: https://macronepal.com/complete-guide-to-bash-data-types/
Complete Guide to Bash Variables
Learn how to declare, assign, and use variables in Bash scripts.
Link: https://macronepal.com/complete-guide-to-bash-variables/
Complete Guide to Bash Scripting
A complete introduction to writing Bash scripts for automation.
Link: https://macronepal.com/complete-guide-to-bash-scripting/
Complete Guide to Basic Bash Syntax
Learn the fundamental syntax needed to write Bash commands and scripts.
Link: https://macronepal.com/complete-guide-to-basic-bash-syntax/
Complete Guide to Bash Chgrp Command
Learn how to change group ownership of files and directories.
Link: https://macronepal.com/complete-guide-to-bash-chgrp-command-change-group-ownership-2/
Complete Guide to Bash Chgrp Command (Alternate)
Another detailed explanation of Bash group ownership management.
Link: https://macronepal.com/complete-guide-to-bash-chgrp-command-change-group-ownership/
Complete Guide to Bash File Permissions and Ownership
Understand Linux file permissions and ownership management.
Link: https://macronepal.com/complete-guide-to-bash-file-permissions-and-ownership/
Complete Guide to Bash Chmod Command
Learn how to modify file permissions using chmod.
Link: https://macronepal.com/complete-guide-to-bash-chmod-command-change-file-permissions/
Complete Guide to Bash Chown Command
Change file ownership using the Bash chown command.
Link: https://macronepal.com/complete-guide-to-bash-chown-command-change-file-ownership/
Complete Guide to Bash SCP Command
Securely transfer files between systems using SCP.
Link: https://macronepal.com/complete-guide-to-bash-scp-command-secure-copy/
Complete Guide to Bash SSH Command
Learn secure remote access and server management using SSH.
Link: https://macronepal.com/complete-guide-to-bash-ssh-command/
Bash Wget Command Complete Guide
Download files from the internet using the wget command.
Link: https://macronepal.com/bash-wget-command-complete-guide-to-non-interactive-network-downloader/
