Crontab is the standard tool on Linux for scheduling the automatic execution of commands and scripts at regular intervals. Whether for nightly backups, log cleanup or monitoring, cron is an essential component of Linux system administration.
Prerequisites
- Operating system: A Linux distribution (Debian, Ubuntu, CentOS, Rocky Linux, etc.)
- Privileges: Standard user access for personal tasks, root or sudo access for system tasks
- Knowledge: Basics of the Linux terminal and editing files from the command line
- Packages: The
cronpackage is usually installed by default on most distributions
Check that the cron service is properly installed and active:
sudo systemctl status cron
If cron is not installed:
# Debian / Ubuntu
sudo apt update && sudo apt install -y cron
# CentOS / Rocky Linux
sudo dnf install -y cronie
# Enable and start the service
sudo systemctl enable cron
sudo systemctl start cron
Understanding Cron
The cron daemon
Cron is a daemon (background service) that starts when the system boots. Every minute, it checks whether there are any scheduled tasks to run at the current time. If it finds any, it executes them with the permissions of the user who owns the task.
The cron daemon consults several sources to determine which tasks to run:
- User crontabs: stored in
/var/spool/cron/crontabs/(one file per user) - System crontab: the
/etc/crontabfile (contains an additional user field) - cron.d directory: the files in
/etc/cron.d/(same format as /etc/crontab)
User crontab vs /etc/crontab
There is an important difference between a user's crontab and the system /etc/crontab file:
# User crontab (crontab -e) — NO user field
# minute hour day month day_of_week command
0 2 * * * /home/user/backup.sh
# System /etc/crontab — WITH user field
# minute hour day month day_of_week user command
0 2 * * * root /usr/local/bin/backup-system.sh
Never edit the files in
/var/spool/cron/crontabs/ directly. Always use the crontab -e command, which validates the syntax before saving.
Crontab Syntax
The 5 time fields
Each line in a crontab follows the format below with 5 scheduling fields:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command_to_run
Special characters
Special characters allow you to define flexible schedules:
*(asterisk): matches all possible values of the field,(comma): separates a list of values — e.g.1,15(the 1st and the 15th)-(dash): defines a range of values — e.g.1-5(Monday to Friday)/(slash): defines a step interval — e.g.*/10(every 10 units)
Predefined shortcuts
Cron offers handy shortcuts for common schedules:
@reboot # Run once at system startup
@yearly # Equivalent to: 0 0 1 1 * (January 1st at midnight)
@monthly # Equivalent to: 0 0 1 * * (1st of each month at midnight)
@weekly # Equivalent to: 0 0 * * 0 (every Sunday at midnight)
@daily # Equivalent to: 0 0 * * * (every day at midnight)
@hourly # Equivalent to: 0 * * * * (every hour at minute 0)
Managing the crontab
Essential commands
# Edit the current user's crontab
crontab -e
# Display the current user's crontab
crontab -l
# Delete the current user's crontab
crontab -r
# Edit another user's crontab (root required)
sudo crontab -u www-data -e
# Display another user's crontab
sudo crontab -u www-data -l
The
crontab -r command deletes your crontab entirely without asking for confirmation. To avoid mistakes, some administrators create an alias crontab -ri (interactive) or regularly back up their crontab with crontab -l > ~/crontab-backup.txt.
User crontab vs root crontab
Each user has their own crontab. Tasks run with that user's permissions:
# As a normal user
crontab -e
# Tasks run with your account's permissions
# As root
sudo crontab -e
# Tasks run with root permissions — use with caution
Practical examples
Daily backup
Schedule an automatic backup every night at 2 a.m.:
# Daily backup at 2:00 a.m.
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Example of an associated backup script:
#!/bin/bash
# /usr/local/bin/backup.sh
DATE=$(date +\%Y\%m\%d)
BACKUP_DIR="/backup"
SOURCE="/var/www/html"
echo "=== Backup started at $(date) ==="
tar -czf "${BACKUP_DIR}/www-${DATE}.tar.gz" "${SOURCE}"
# Delete backups older than 30 days
find "${BACKUP_DIR}" -name "www-*.tar.gz" -mtime +30 -delete
echo "=== Backup finished at $(date) ==="
Log cleanup
# Clean up temporary files every Sunday at 3:00 a.m.
0 3 * * 0 find /tmp -type f -atime +7 -delete
# Rotate application logs on the first of each month
0 4 1 * * /usr/local/bin/rotate-logs.sh
System monitoring
# Check disk space every 6 hours
0 */6 * * * /usr/local/bin/check-disk.sh
# Monitor CPU load every 5 minutes
*/5 * * * * /usr/local/bin/check-cpu.sh >> /var/log/cpu-monitor.log 2>&1
# Check that a service is active every 2 minutes
*/2 * * * * systemctl is-active --quiet nginx || systemctl restart nginx
Sending reports
# Weekly report every Monday at 8:00 a.m.
0 8 * * 1 /usr/local/bin/weekly-report.sh | mail -s "Weekly report" [email protected]
# Daily disk usage report
0 7 * * * df -h | mail -s "Disk report $(hostname)" [email protected]
Always redirect the output of your cron tasks to a log file with
>> /var/log/file.log 2>&1. This makes debugging easier and prevents cron from sending an email on every run.
Environment variables
The crontab lets you define environment variables at the top of the file. This is essential because cron uses a minimal environment that is very different from your interactive shell:
# Define the shell to use
SHELL=/bin/bash
# Define the PATH (critical — cron uses a very restricted PATH by default)
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Email address to receive task output
[email protected]
# Home directory
HOME=/home/myuser
# Scheduled tasks below
0 2 * * * /usr/local/bin/backup.sh
*/30 * * * * /usr/local/bin/check-services.sh
By default, cron sends an email to the user who owns the crontab for each task that produces output. Set
MAILTO="" (empty) to disable emails entirely, or redirect the output to a log file.
The most important variables
- PATH: Determines where cron looks for executables. By default, cron's PATH is
/usr/bin:/bin, which is very limited - SHELL: The shell used to run the commands (default
/bin/sh) - MAILTO: The email address that receives task output (empty = no email)
- HOME: The working directory for task execution
Cron directories
In addition to the personal crontab, Linux provides predefined directories to organize tasks by their execution frequency:
/etc/cron.d/ # Additional crontab files (system format with user field)
/etc/cron.daily/ # Scripts run daily
/etc/cron.hourly/ # Scripts run every hour
/etc/cron.weekly/ # Scripts run every week
/etc/cron.monthly/ # Scripts run every month
Using cron directories
To add a daily task, simply place an executable script in the appropriate directory:
# Create a daily cleanup script
sudo tee /etc/cron.daily/clean-tmp > /dev/null << 'SCRIPT'
#!/bin/bash
# Delete temporary files older than 7 days
find /tmp -type f -atime +7 -delete 2>/dev/null
find /var/tmp -type f -atime +30 -delete 2>/dev/null
SCRIPT
# Make the script executable (REQUIRED)
sudo chmod +x /etc/cron.daily/clean-tmp
Scripts in the
cron.daily, cron.weekly, etc. directories are run by run-parts, which ignores files containing a dot in their name. Name your scripts without an extension (e.g. clean-tmp and not clean-tmp.sh).
Scheduling via /etc/cron.d
The /etc/cron.d/ directory lets you add files in the system crontab format, with a user field:
# /etc/cron.d/monitoring
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Check every 5 minutes
*/5 * * * * root /usr/local/bin/check-services.sh >> /var/log/monitoring.log 2>&1
Anacron
Anacron is a complement to cron designed for machines that do not run 24/7 (workstations, laptops). Unlike cron, anacron does not run in real time: at boot it checks whether any scheduled tasks were missed and runs them as catch-up.
Configuring anacron
The main configuration file is /etc/anacrontab:
# /etc/anacrontab
# period(days) delay(minutes) identifier command
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
1 5 cron.daily run-parts /etc/cron.daily
7 10 cron.weekly run-parts /etc/cron.weekly
30 15 cron.monthly run-parts /etc/cron.monthly
- Period: the frequency in days (1 = daily, 7 = weekly, 30 = monthly)
- Delay: the number of minutes to wait after boot before running the task
- Identifier: a unique name for tracking in
/var/spool/anacron/
# Install anacron if missing
sudo apt install -y anacron
# Force anacron to run manually
sudo anacron -f -n
Logging and debugging
Viewing cron logs
The cron daemon's actions are recorded in syslog:
# Debian / Ubuntu
sudo grep CRON /var/log/syslog
# CentOS / Rocky Linux
sudo grep CRON /var/log/cron
# Follow the logs in real time
sudo tail -f /var/log/syslog | grep CRON
Redirecting stdout and stderr
Master redirections to capture all of your tasks' output:
# Redirect stdout and stderr to a log file
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Separate stdout and stderr into distinct files
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>> /var/log/backup-errors.log
# Discard all output (silent)
0 2 * * * /usr/local/bin/backup.sh > /dev/null 2>&1
# Add a timestamp to the logs
0 2 * * * echo "--- $(date) ---" >> /var/log/backup.log && /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Notification emails
By default, if a cron task produces output (stdout or stderr) and the MAILTO variable is set, cron sends an email. Make sure your system has a working MTA (Mail Transfer Agent):
# Test mail sending from cron
* * * * * echo "Test cron mail $(date)" 2>&1
# Check the mail queue
sudo mailq
# Read local mail
mail
Securing
Access control with cron.allow and cron.deny
Linux lets you control which users can use crontab via two files:
# /etc/cron.allow — if this file exists, ONLY the listed users can use crontab
sudo tee /etc/cron.allow > /dev/null << EOF
root
admin
deployer
EOF
# /etc/cron.deny — the listed users are FORBIDDEN from using crontab
sudo tee /etc/cron.deny > /dev/null << EOF
guest
testuser
EOF
The precedence logic is as follows:
- If
/etc/cron.allowexists, only the users listed in it can access crontab - If only
/etc/cron.denyexists, all users except those listed have access - If neither file exists, the behavior depends on the distribution
Script permissions
# Check a cron script's permissions
ls -la /usr/local/bin/backup.sh
# Recommended permissions: read + execute for the owner only
sudo chmod 700 /usr/local/bin/backup.sh
sudo chown root:root /usr/local/bin/backup.sh
# For a script run by a specific user
sudo chmod 750 /home/deployer/scripts/deploy.sh
sudo chown deployer:deployer /home/deployer/scripts/deploy.sh
A cron script run by root with overly broad write permissions represents a major privilege escalation risk. Make sure only root can modify the scripts run by the root crontab.
Best practices
Use wrapper scripts
Rather than putting complex commands directly in the crontab, use dedicated scripts:
#!/bin/bash
# /usr/local/bin/cron-backup-wrapper.sh
# Wrapper script for the cron backup
set -euo pipefail
LOGFILE="/var/log/backup-cron.log"
LOCKFILE="/tmp/backup-cron.lock"
# Check that another instance isn't already running
if [ -f "$LOCKFILE" ]; then
echo "$(date) - ERROR: backup already in progress (lockfile exists)" >> "$LOGFILE"
exit 1
fi
# Create the lock file
trap "rm -f $LOCKFILE" EXIT
touch "$LOCKFILE"
echo "$(date) - Starting backup" >> "$LOGFILE"
/usr/local/bin/backup.sh >> "$LOGFILE" 2>&1
echo "$(date) - Backup finished (exit code: $?)" >> "$LOGFILE"
Lock files to prevent concurrent runs
The flock utility ensures that only one instance of your task runs at a time:
# Using flock in the crontab
*/5 * * * * flock -n /tmp/check-services.lock /usr/local/bin/check-services.sh
# With a timeout (wait 60 seconds maximum)
0 * * * * flock -w 60 /tmp/sync.lock /usr/local/bin/sync-data.sh
Structured logging
#!/bin/bash
# Logging function with a timestamp
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> /var/log/cron-tasks.log
}
log "INFO: Starting the task"
# ... commands ...
log "INFO: Task completed successfully"
Back up your crontab
# Export the current crontab
crontab -l > ~/crontab-backup-$(date +%Y%m%d).txt
# Restore from a backup
crontab ~/crontab-backup-20260212.txt
# Automate the crontab backup (meta-cron)
0 0 * * 0 crontab -l > /backup/crontabs/$(whoami)-$(date +\%Y\%m\%d).txt
Troubleshooting
The task isn't running
Here is a systematic diagnostic checklist:
- Check the cron service:
sudo systemctl status cron
# If inactive:
sudo systemctl start cron
- Check the crontab syntax:
# List it to check visually
crontab -l
# Validate it with an online tool or test it in an editor
- Check the logs:
sudo grep CRON /var/log/syslog | tail -20
- Test the script manually with the same environment:
# Simulate cron's minimal environment
env -i SHELL=/bin/bash HOME=/root PATH=/usr/bin:/bin /usr/local/bin/my-script.sh
PATH issues
This is the number one cause of failing cron tasks. By default, cron's PATH is very limited:
# BAD — command without an absolute path
0 2 * * * mysqldump -u root mydb > /backup/db.sql
# GOOD — absolute path to the command
0 2 * * * /usr/bin/mysqldump -u root mydb > /backup/db.sql
# BETTER — define the PATH at the top of the crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * mysqldump -u root mydb > /backup/db.sql
Permission issues
# Check that the script is executable
ls -la /usr/local/bin/my-script.sh
# Add the execute permission if missing
chmod +x /usr/local/bin/my-script.sh
# Check that the user can access the output directory
ls -la /var/log/
# The log directory must be writable
Encoding and special character issues
# The % character has a special meaning in crontab (newline)
# It must be escaped with a backslash
# BAD
0 2 * * * /usr/bin/date +%Y%m%d > /tmp/date.txt
# GOOD
0 2 * * * /usr/bin/date +\%Y\%m\%d > /tmp/date.txt
To quickly check that cron is actually running your tasks, add a test task that writes to a file every minute, then remove it once the problem is solved:
* * * * * echo "cron works $(date)" >> /tmp/cron-test.log
Conclusion
Crontab is a fundamental Linux administration tool that lets you efficiently automate recurring tasks. To sum up the key points:
- Master the syntax of the 5 fields and special characters to schedule your tasks precisely
- Always define the PATH at the top of the crontab to avoid execution issues
- Use wrapper scripts with lock files and logging for reliable tasks
- Redirect outputs to log files to make debugging easier
- Secure your scripts with strict permissions and control access via cron.allow/cron.deny
- Consider anacron for machines that don't run continuously
- Back up your crontab regularly to avoid losing your configuration
By combining cron with good scripting and logging practices, you build a robust automation infrastructure that is easy to maintain.
Comments