Server
Difficulty: Beginner
11 min read

Crontab: Scheduling automated tasks on Linux

Learn how to schedule and automate your recurring tasks on Linux with crontab, cron and anacron. From basic syntax to advanced debugging techniques.

Back to tutorials
About Crontab
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 cron package 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/crontab file (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
Warning
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
Be careful with crontab -r
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]
Best practice
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
MAILTO variable
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
Script naming
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:

  1. If /etc/cron.allow exists, only the users listed in it can access crontab
  2. If only /etc/cron.deny exists, all users except those listed have access
  3. 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
Critical security
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:

  1. Check the cron service:
sudo systemctl status cron
# If inactive:
sudo systemctl start cron
  1. Check the crontab syntax:
# List it to check visually
crontab -l

# Validate it with an online tool or test it in an editor
  1. Check the logs:
sudo grep CRON /var/log/syslog | tail -20
  1. 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
Debugging tip
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.

Written by

Morgann Riu

Cybersecurity and Linux administration expert. I share my knowledge through free tutorials and training to help system administrators and developers secure their infrastructures.

Share this tutorial

Did you enjoy this article?

Comments

Checklist Sécurité Linux

30 points essentiels pour sécuriser un serveur Linux. Recevez aussi les nouveaux tutoriels par email.

Pas de spam. Désabonnement en 1 clic.