Complete Guide to Bash crontab Command (Schedule Tasks)

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

  1. Simple Syntax: Five time fields followed by command
  2. Flexible Scheduling: Ranges, lists, steps, and special keywords
  3. User-Specific: Each user can have their own crontab
  4. System-Wide: /etc/crontab and cron directories for system tasks
  5. Environment: Cron provides limited environment, set explicitly
  6. Logging: Always redirect output for debugging
  7. Security: Use least privilege, validate inputs, prevent overlaps

Best Practices Summary

AreaBest Practice
PathsUse absolute paths for all commands and files
LoggingRedirect stdout/stderr to log files
EnvironmentSet PATH and other variables explicitly
TestingTest commands manually before adding to crontab
Error HandlingCheck exit codes and handle failures
LockingPrevent overlapping runs with flock
PermissionsRun jobs with minimal privileges
MonitoringMonitor cron logs and job execution
DocumentationComment complex crontab entries
BackupRegularly backup crontab configurations

Common Issues and Solutions

IssueSolution
Job not runningCheck cron service, syntax, permissions
PATH issuesSet full PATH in crontab
Script failsTest manually with same environment
Overlapping runsUse flock or PID files
No outputCheck MAILTO or redirect to file
Wrong timeVerify system timezone
Permission deniedCheck script executable bits
Command not foundUse full paths

Mastering crontab is essential for system administration and automation. Start with simple schedules and gradually incorporate more complex patterns as needed.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper