Learn to configure cron jobs and systemd timers for automated task scheduling on Linux systems with proper logging, security, and troubleshooting techniques.
Prerequisites
- Root or sudo access
- Basic command line knowledge
- Text editor (nano/vim)
What this solves
Cron jobs automate repetitive system tasks like backups, log rotation, system maintenance, and application updates. This tutorial shows you how to configure user and system cron jobs, understand cron syntax, implement proper logging and security, and use modern systemd timers as an alternative scheduling solution.
Understanding cron and crontab syntax
Cron uses a specific time format with five fields representing minute, hour, day of month, month, and day of week. Each field accepts numbers, ranges, lists, and special characters for flexible scheduling.
Here are common cron syntax examples:
| Schedule | Cron Expression | Description |
|---|---|---|
| Every minute | * | Runs every minute of every day |
| Daily at 3 AM | 0 3 * | Runs at 03:00 every day |
| Weekly on Sunday | 0 2 0 | Runs at 02:00 every Sunday |
| Monthly on 1st | 0 0 1 | Runs at midnight on the 1st of each month |
| Every 15 minutes | /15 * | Runs every 15 minutes |
| Weekdays at 9 AM | 0 9 1-5 | Runs at 09:00 Monday through Friday |
Special strings can replace the five-field format:
- @reboot - Run once at startup
- @yearly or @annually - Run once a year (0 0 1 1 *)
- @monthly - Run once a month (0 0 1 )
- @weekly - Run once a week (0 0 0)
- @daily or @midnight - Run once a day (0 0 *)
- @hourly - Run once an hour (0 )
Step-by-step configuration
Verify cron service is running
First, ensure the cron daemon is installed and running on your system.
sudo systemctl status cron
sudo systemctl enable --now cron
Create your first user cron job
Use crontab -e to edit your personal cron table. This opens your default editor with your current cron jobs.
crontab -e
Add a simple backup job that runs daily at 2 AM:
# Daily backup at 2 AM
0 2 * /usr/bin/rsync -av /home/user/documents/ /backup/documents/ >> /var/log/backup.log 2>&1
System update check every Sunday at 1 AM
0 1 0 /usr/bin/apt list --upgradable >> /var/log/updates.log 2>&1
Disk usage report every Monday at 8 AM
0 8 1 /usr/bin/df -h | /usr/bin/mail -s "Disk Usage Report" admin@example.com
Set up proper environment variables
Cron jobs run with a minimal environment. Set necessary variables at the top of your crontab to ensure commands find required paths and configurations.
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com
HOME=/home/user
Your cron jobs go below these environment settings
0 2 * /home/user/scripts/backup.sh
/30 * /usr/local/bin/monitor_disk.py
Create system-wide cron jobs
System cron jobs go in /etc/crontab or /etc/cron.d/ directory. These require specifying the user account to run as.
sudo nano /etc/cron.d/system-maintenance
# System maintenance cron jobs
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=root
Clean temporary files daily at 3 AM as root
0 3 * root /usr/bin/find /tmp -type f -atime +7 -delete
Rotate logs weekly on Sunday at 4 AM as root
0 4 0 root /usr/sbin/logrotate /etc/logrotate.conf
Update package database daily at 5 AM as root
0 5 * root /usr/bin/apt update > /var/log/apt-update.log 2>&1
Use predefined cron directories
Linux provides convenient directories for common schedules. Place executable scripts in these directories instead of managing crontab entries.
ls -la /etc/cron.hourly/
ls -la /etc/cron.daily/
ls -la /etc/cron.weekly/
ls -la /etc/cron.monthly/
Create a daily system cleanup script:
sudo nano /etc/cron.daily/cleanup
#!/bin/bash
Daily system cleanup script
Clean package cache
apt-get clean
Remove old kernels (keep 2 latest)
apt-get autoremove -y
Clean journal logs older than 30 days
journalctl --vacuum-time=30d
Update locate database
updatedb
echo "$(date): System cleanup completed" >> /var/log/cleanup.log
Make the script executable:
sudo chmod 755 /etc/cron.daily/cleanup
Configure logging and monitoring
Set up comprehensive logging to track cron job execution and troubleshoot issues effectively.
sudo nano /etc/rsyslog.d/50-cron.conf
# Enable cron logging
cron.* /var/log/cron.log
sudo systemctl restart rsyslog
sudo systemctl restart cron
Implement cron job security practices
Control who can use cron and secure cron job execution with proper file permissions and access controls.
sudo nano /etc/cron.allow
root
user1
backup
monitoring
Set proper permissions on cron files:
sudo chmod 600 /etc/cron.allow
sudo chmod 600 /etc/crontab
sudo chmod 700 /etc/cron.d/
sudo chmod 700 /var/spool/cron/
Configure systemd timers as cron alternative
Create a systemd service unit
Systemd timers provide more advanced scheduling options and better logging than traditional cron. Start by creating a service unit for your task.
sudo nano /etc/systemd/system/backup.service
[Unit]
Description=Daily backup service
Wants=backup.timer
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-script.sh
StandardOutput=journal
StandardError=journal
Create the corresponding timer unit
The timer unit defines when and how often the service runs, with more flexible options than cron syntax.
sudo nano /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily
Requires=backup.service
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target
Enable and start the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
sudo systemctl status backup.timer
Advanced systemd timer scheduling
Systemd timers support complex scheduling patterns and multiple trigger conditions.
sudo nano /etc/systemd/system/monitoring.timer
[Unit]
Description=System monitoring every 15 minutes
Requires=monitoring.service
[Timer]
Run every 15 minutes
OnCalendar=*:0/15
Run 2 minutes after boot
OnBootSec=2min
Catch up missed runs
Persistent=true
Spread load randomly within 30 seconds
RandomizedDelaySec=30
Prevent overlapping runs
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target
Verify your setup
Test your cron jobs and systemd timers to ensure they work correctly and troubleshoot any issues.
# List current user cron jobs
crontab -l
List all system cron jobs
sudo cat /etc/crontab
ls -la /etc/cron.d/
Check cron service status
sudo systemctl status cron # Ubuntu/Debian
sudo systemctl status crond # RHEL/CentOS
View cron logs
sudo tail -f /var/log/cron.log # Ubuntu/Debian
sudo tail -f /var/log/cron # RHEL/CentOS
List active systemd timers
sudo systemctl list-timers
Check specific timer status
sudo systemctl status backup.timer
View timer logs
sudo journalctl -u backup.timer -u backup.service
Test cron job execution
Create a simple test job to verify cron is working properly.
crontab -e
Add a test job that runs every minute:
# Test cron job - remove after testing
* echo "$(date): Cron test successful" >> /tmp/crontest.log
Wait a few minutes, then check the output:
cat /tmp/crontest.log
Remove the test job after verification:
crontab -e
Delete the test line and save
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Cron job doesn't run | Cron service not running | sudo systemctl enable --now cron (or crond) |
| Script not found | Missing PATH in cron environment | Use full paths to commands or set PATH in crontab |
| Permission denied | Incorrect script permissions | chmod 755 script.sh and check ownership |
| No email notifications | Mail system not configured | Install mailutils: sudo apt install mailutils |
| Environment variables missing | Cron runs with minimal environment | Set variables at top of crontab or source .bashrc |
| Job runs but fails silently | No output redirection | Add >> /var/log/jobname.log 2>&1 to capture output |
| Systemd timer not starting | Service unit missing or invalid | systemctl status unit.service to check service |
| Timer shows inactive | Timer not enabled | sudo systemctl enable --now timer.timer |
Security considerations and best practices
Follow these security practices when implementing automated tasks:
- Use dedicated service accounts: Create specific users for different types of jobs instead of running everything as root
- Implement log rotation: Configure log rotation to prevent cron logs from filling disk space
- Set resource limits: Use systemd or ulimit to prevent runaway processes from consuming system resources
- Validate input: Always sanitize input and validate file paths in your scheduled scripts
- Monitor execution: Set up system monitoring to track job success and failure rates
- Use file locking: Implement flock or similar mechanisms to prevent overlapping job execution
- Secure script storage: Store scripts in protected directories with appropriate permissions (755 for directories, 644 for scripts)
Next steps
- Configure Linux system backup automation with rsync and systemd timers
- Configure Linux log rotation with logrotate and compression for system maintenance
- Configure automated system maintenance with advanced cron scheduling
- Monitor cron jobs and systemd timers with Prometheus alerting
- Configure centralized cron management with Ansible automation
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Global variables
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKUP_DIR="/tmp/cron-install-backup-$(date +%Y%m%d-%H%M%S)"
PKG_MGR=""
PKG_INSTALL=""
CRON_SERVICE=""
EMAIL_ADDRESS=""
# Error handling
trap 'error_exit "Installation failed at line $LINENO"' ERR
error_exit() {
echo -e "${RED}ERROR: $1${NC}" >&2
cleanup
exit 1
}
cleanup() {
echo -e "${YELLOW}Cleaning up temporary files...${NC}"
# Restore backups if they exist
if [ -d "$BACKUP_DIR" ]; then
if [ -f "$BACKUP_DIR/crontab" ]; then
crontab "$BACKUP_DIR/crontab" 2>/dev/null || true
fi
fi
}
usage() {
echo "Usage: $0 [email@example.com]"
echo " email: Optional email address for cron notifications"
exit 1
}
detect_distro() {
echo -e "${BLUE}[1/8] Detecting distribution...${NC}"
if [ ! -f /etc/os-release ]; then
error_exit "Cannot detect distribution - /etc/os-release not found"
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
CRON_SERVICE="cron"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
CRON_SERVICE="crond"
;;
fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
CRON_SERVICE="crond"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
CRON_SERVICE="crond"
;;
*)
error_exit "Unsupported distribution: $ID"
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME (using $PKG_MGR)${NC}"
}
check_prerequisites() {
echo -e "${BLUE}[2/8] Checking prerequisites...${NC}"
if [ "$EUID" -ne 0 ]; then
error_exit "This script must be run as root or with sudo"
fi
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Backup existing crontab if it exists
if crontab -l >/dev/null 2>&1; then
crontab -l > "$BACKUP_DIR/crontab"
echo -e "${GREEN}Backed up existing crontab${NC}"
fi
}
install_cron() {
echo -e "${BLUE}[3/8] Installing and configuring cron service...${NC}"
# Update package cache
if [ "$PKG_MGR" = "apt" ]; then
apt update
$PKG_INSTALL cron rsync mailutils
else
$PKG_INSTALL cronie rsync mailx
fi
# Enable and start cron service
systemctl enable --now "$CRON_SERVICE"
if systemctl is-active --quiet "$CRON_SERVICE"; then
echo -e "${GREEN}Cron service is running${NC}"
else
error_exit "Failed to start cron service"
fi
}
setup_logging() {
echo -e "${BLUE}[4/8] Setting up cron logging...${NC}"
# Create log directory
mkdir -p /var/log/cron-jobs
chmod 755 /var/log/cron-jobs
# Configure rsyslog for cron logging (if available)
if systemctl is-active --quiet rsyslog 2>/dev/null; then
if [ "$PKG_MGR" = "apt" ]; then
# Enable cron logging in rsyslog
if ! grep -q "cron" /etc/rsyslog.conf; then
echo "cron.* /var/log/cron.log" >> /etc/rsyslog.conf
systemctl restart rsyslog
fi
fi
fi
}
create_system_scripts() {
echo -e "${BLUE}[5/8] Creating system maintenance scripts...${NC}"
# Create daily cleanup script
cat > /etc/cron.daily/system-cleanup << 'EOF'
#!/bin/bash
# Daily system cleanup script
# Clean package cache
if command -v apt-get >/dev/null 2>&1; then
apt-get clean
apt-get autoremove -y
elif command -v dnf >/dev/null 2>&1; then
dnf clean packages
elif command -v yum >/dev/null 2>&1; then
yum clean packages
fi
# Clean temporary files older than 7 days
find /tmp -type f -atime +7 -delete 2>/dev/null || true
# Clean journal logs older than 30 days
if command -v journalctl >/dev/null 2>&1; then
journalctl --vacuum-time=30d
fi
# Update locate database if available
if command -v updatedb >/dev/null 2>&1; then
updatedb
fi
echo "$(date): System cleanup completed" >> /var/log/cron-jobs/cleanup.log
EOF
chmod 755 /etc/cron.daily/system-cleanup
# Create weekly log rotation script
cat > /etc/cron.weekly/log-rotation << 'EOF'
#!/bin/bash
# Weekly log rotation for custom cron logs
# Rotate cron job logs
if [ -f /var/log/cron-jobs/backup.log ]; then
mv /var/log/cron-jobs/backup.log /var/log/cron-jobs/backup.log.old
gzip /var/log/cron-jobs/backup.log.old 2>/dev/null || true
fi
if [ -f /var/log/cron-jobs/cleanup.log ]; then
mv /var/log/cron-jobs/cleanup.log /var/log/cron-jobs/cleanup.log.old
gzip /var/log/cron-jobs/cleanup.log.old 2>/dev/null || true
fi
echo "$(date): Log rotation completed" >> /var/log/cron-jobs/rotation.log
EOF
chmod 755 /etc/cron.weekly/log-rotation
}
setup_system_crontab() {
echo -e "${BLUE}[6/8] Setting up system-wide cron jobs...${NC}"
cat > /etc/cron.d/system-maintenance << EOF
# System maintenance cron jobs
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=root
# Update package database daily at 5 AM
0 5 * * * root /usr/bin/logger "Starting package database update" && ([ -x /usr/bin/apt ] && /usr/bin/apt update || [ -x /usr/bin/dnf ] && /usr/bin/dnf check-update || [ -x /usr/bin/yum ] && /usr/bin/yum check-update) >> /var/log/cron-jobs/package-update.log 2>&1
# Check disk usage daily at 6 AM
0 6 * * * root /bin/df -h >> /var/log/cron-jobs/disk-usage.log 2>&1
# Clean old compressed logs monthly
0 2 1 * * root /usr/bin/find /var/log -name "*.gz" -mtime +90 -delete 2>/dev/null
EOF
chmod 644 /etc/cron.d/system-maintenance
}
setup_user_crontab() {
echo -e "${BLUE}[7/8] Setting up example user cron jobs...${NC}"
# Create a sample user crontab with proper environment
cat > /tmp/user-crontab << EOF
# Environment variables for cron jobs
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=$HOME
EOF
if [ -n "$EMAIL_ADDRESS" ]; then
echo "MAILTO=$EMAIL_ADDRESS" >> /tmp/user-crontab
fi
cat >> /tmp/user-crontab << 'EOF'
# Example cron jobs (commented out by default)
# Backup home directory daily at 2 AM
# 0 2 * * * /usr/bin/rsync -av $HOME/Documents/ /backup/documents/ >> /var/log/cron-jobs/backup.log 2>&1
# Check system updates every Sunday at 1 AM
# 0 1 * * 0 /usr/bin/logger "Checking for system updates" && (/usr/bin/apt list --upgradable 2>/dev/null || /usr/bin/dnf list updates 2>/dev/null || /usr/bin/yum list updates 2>/dev/null) >> /var/log/cron-jobs/updates.log 2>&1
# Monitor disk usage every 30 minutes during business hours
# */30 9-17 * * 1-5 /bin/df -h | /usr/bin/logger "Disk usage check"
EOF
# Install the crontab for root (since we're running as root)
crontab /tmp/user-crontab
rm /tmp/user-crontab
}
verify_installation() {
echo -e "${BLUE}[8/8] Verifying installation...${NC}"
# Check cron service status
if ! systemctl is-active --quiet "$CRON_SERVICE"; then
error_exit "Cron service is not running"
fi
# Check if crontab was installed
if ! crontab -l >/dev/null 2>&1; then
error_exit "Crontab installation failed"
fi
# Check system cron file
if [ ! -f /etc/cron.d/system-maintenance ]; then
error_exit "System cron file was not created"
fi
# Check daily script
if [ ! -x /etc/cron.daily/system-cleanup ]; then
error_exit "Daily cleanup script is not executable"
fi
echo -e "${GREEN}✓ Cron service is running${NC}"
echo -e "${GREEN}✓ User crontab configured${NC}"
echo -e "${GREEN}✓ System cron jobs configured${NC}"
echo -e "${GREEN}✓ Maintenance scripts created${NC}"
echo -e "${GREEN}✓ Log directory created: /var/log/cron-jobs${NC}"
echo -e "\n${GREEN}Cron installation completed successfully!${NC}"
echo -e "\nUseful commands:"
echo -e " View user crontab: ${BLUE}crontab -l${NC}"
echo -e " Edit user crontab: ${BLUE}crontab -e${NC}"
echo -e " Check cron logs: ${BLUE}tail -f /var/log/cron${NC}"
echo -e " View custom logs: ${BLUE}ls -la /var/log/cron-jobs/${NC}"
if [ -n "$EMAIL_ADDRESS" ]; then
echo -e " Email notifications configured for: ${BLUE}$EMAIL_ADDRESS${NC}"
fi
}
main() {
# Parse arguments
if [ $# -gt 1 ]; then
usage
fi
if [ $# -eq 1 ]; then
if [[ $1 =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
EMAIL_ADDRESS="$1"
else
error_exit "Invalid email address format"
fi
fi
echo -e "${GREEN}Starting cron installation and configuration...${NC}"
detect_distro
check_prerequisites
install_cron
setup_logging
create_system_scripts
setup_system_crontab
setup_user_crontab
verify_installation
# Cleanup backup directory on success
rm -rf "$BACKUP_DIR"
echo -e "${GREEN}Installation complete!${NC}"
}
main "$@"
Review the script before running. Execute with: bash install.sh