CRON — LINUX TASK SCHEDULING Backups 0 2 * * * backup.sh Cleanup 0 3 * * 0 cleanup.sh Monitoring */5 * * * * check.sh SSL Renewal 0 4 1 * * certbot Automate recurring tasks with precise time-based scheduling

Automating repetitive tasks is a fundamental part of Linux server administration. Whether you need nightly database backups, weekly log rotation, or minute-by-minute health checks, cron is the time-based job scheduler that handles it all. It has been a core part of Unix-like systems since the 1970s, and every Linux distribution ships with it.

This guide covers everything from basic crontab syntax to advanced techniques like environment variable management, output logging, security restrictions, and real-world scheduling patterns. By the end, you will be able to schedule, debug, and maintain cron jobs confidently on any Linux system.


Prerequisites

Before you begin, make sure you have the following:

  • A Linux system (Ubuntu, Debian, CentOS, RHEL, or any distribution)
  • A terminal with shell access (local or SSH)
  • A non-root user account with sudo privileges
  • Basic familiarity with the command line and a text editor (nano or vim)

Verify that cron is installed and running:

# Check if cron is active
systemctl status cron

# On RHEL/CentOS, the service is called crond
systemctl status crond

If cron is not installed, install it:

# Debian/Ubuntu
sudo apt update && sudo apt install -y cron

# RHEL/CentOS/Fedora
sudo dnf install -y cronie

# Enable and start the service
sudo systemctl enable cron && sudo systemctl start cron

What Is Cron?

Cron is a time-based job scheduling daemon found on virtually every Unix and Linux system. The name comes from the Greek word chronos (time). Cron reads configuration files called crontabs (cron tables) and executes commands at specified intervals — once a minute, once an hour, once a day, or any combination of time fields you define.

Cron solves a simple but critical problem: tasks that need to happen regularly should not depend on someone remembering to run them. Automated backups, log cleanup, system monitoring, certificate renewal, and database maintenance are all tasks that belong in cron.


How Cron Works

Understanding the architecture helps you debug issues faster.

The Cron Daemon

The cron daemon (crond or cron) starts at boot and runs continuously in the background. Every minute, it wakes up and checks all crontab files for jobs scheduled at the current time. If a match is found, it executes the command.

# Verify the daemon is running
ps aux | grep cron

User Crontabs

Each user on the system can have their own crontab. These are stored in /var/spool/cron/crontabs/ (Debian/Ubuntu) or /var/spool/cron/ (RHEL/CentOS). You should never edit these files directly — always use the crontab command.

The System Crontab

The file /etc/crontab is the system-wide crontab. Unlike user crontabs, it includes a username field between the time fields and the command:

# /etc/crontab format (note the username field)
# m   h   dom mon dow user    command
  17  *   *   *   *   root    cd / && run-parts --report /etc/cron.hourly
  25  6   *   *   *   root    test -x /usr/sbin/anacron || run-parts --report /etc/cron.daily

The /etc/cron.d/ Directory

Package maintainers and system administrators can drop individual cron files into /etc/cron.d/. These follow the same format as /etc/crontab (with the username field). This is the preferred way to add system-level cron jobs because the files are isolated and easy to manage.


Crontab Syntax Explained

Every crontab entry is a single line with five time-and-date fields followed by the command to execute:

┌───────────── minute (0–59)
│ ┌───────────── hour (0–23)
│ │ ┌───────────── day of month (1–31)
│ │ │ ┌───────────── month (1–12 or jan–dec)
│ │ │ │ ┌───────────── day of week (0–7, 0 and 7 = Sunday, or sun–sat)
│ │ │ │ │
* * * * * command_to_execute

Field Values and Operators

OperatorMeaningExample
*Any value* * * * * = every minute
,List of values1,15,30 * * * * = minutes 1, 15, 30
-Range of values1-5 * * * * = minutes 1 through 5
/Step values*/10 * * * * = every 10 minutes

Examples of Time Specifications

# Every minute
* * * * * /path/to/script.sh

# Every day at 2:30 AM
30 2 * * * /path/to/script.sh

# Every Monday at 5:00 PM
0 17 * * 1 /path/to/script.sh

# Every 15 minutes
*/15 * * * * /path/to/script.sh

# First day of every month at midnight
0 0 1 * * /path/to/script.sh

# Every weekday (Mon-Fri) at 8:00 AM
0 8 * * 1-5 /path/to/script.sh

# Every 6 hours
0 */6 * * * /path/to/script.sh

# January 1st at midnight
0 0 1 1 * /path/to/script.sh

Special Strings

Cron also supports shorthand strings for common schedules:

StringEquivalentDescription
@rebootN/ARun once at startup
@yearly0 0 1 1 *Once a year (Jan 1, midnight)
@monthly0 0 1 * *Once a month (1st, midnight)
@weekly0 0 * * 0Once a week (Sunday, midnight)
@daily0 0 * * *Once a day (midnight)
@hourly0 * * * *Once an hour (minute 0)
# Run a script at every system reboot
@reboot /home/user/startup-tasks.sh

# Daily log rotation
@daily /usr/local/bin/rotate-logs.sh

Managing Crontab

The crontab command is your primary interface for creating and managing scheduled jobs.

Editing Your Crontab

# Open your crontab in the default editor
crontab -e

# Edit another user's crontab (requires root)
sudo crontab -u www-data -e

The first time you run crontab -e, it will ask you to choose an editor. Select nano if you are not familiar with vim.

Listing Current Jobs

# List your cron jobs
crontab -l

# List another user's cron jobs
sudo crontab -u www-data -l

# List the system crontab
cat /etc/crontab

Removing a Crontab

# Remove your entire crontab (use with caution)
crontab -r

# Interactive removal -- asks for confirmation
crontab -i -r

# Remove another user's crontab
sudo crontab -u username -r

Warning: crontab -r deletes all your cron jobs immediately with no confirmation and no undo. If you only want to remove one entry, use crontab -e and delete the specific line instead.

Backing Up Your Crontab

Always back up your crontab before making changes:

# Save current crontab to a file
crontab -l > ~/crontab-backup-$(date +%Y%m%d).txt

# Restore from backup
crontab ~/crontab-backup-20260124.txt

Environment Variables in Cron

One of the most common causes of cron job failures is the environment. Cron jobs run with a minimal environment that is very different from your interactive shell. Your ~/.bashrc, ~/.bash_profile, and login scripts are not sourced.

The Default Cron Environment

By default, cron typically sets only:

SHELL=/bin/sh
PATH=/usr/bin:/bin
HOME=/home/username
LOGNAME=username

Notice that /usr/local/bin, /snap/bin, and other directories you might expect in your PATH are missing. This is why a command that works perfectly in your terminal may fail silently in cron.

Setting Variables in Crontab

You can define environment variables at the top of your crontab:

# Set a complete PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# Set the shell
SHELL=/bin/bash

# Set email for cron output
MAILTO=admin@example.com

# Your cron jobs below
0 2 * * * /home/user/backup.sh

Using Full Paths in Commands

The safest approach is to always use absolute paths for every command in your cron jobs:

# Bad -- relies on PATH
0 2 * * * mysqldump mydb > /tmp/backup.sql

# Good -- uses full path
0 2 * * * /usr/bin/mysqldump mydb > /tmp/backup.sql

You can find the full path of any command with which:

which mysqldump
# Output: /usr/bin/mysqldump

which python3
# Output: /usr/bin/python3

Output and Logging

By default, cron emails any output (stdout and stderr) from a job to the crontab owner’s local mailbox. On most servers, this goes unread. Proper logging is essential.

Redirecting Output to a Log File

# Redirect stdout and stderr to a log file
0 2 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1

# Separate stdout and stderr into different files
0 2 * * * /home/user/backup.sh >> /var/log/backup.log 2>> /var/log/backup-errors.log

# Discard all output (silent execution)
0 2 * * * /home/user/backup.sh > /dev/null 2>&1

Best Practice: Never discard output during development and testing. Once your cron job is proven stable, you can redirect stderr only and discard stdout: command > /dev/null 2>> /var/log/errors.log.

Using MAILTO for Email Notifications

The MAILTO variable controls where cron sends job output:

# Send output to a specific email address
MAILTO=admin@example.com

# Send to multiple recipients
MAILTO=admin@example.com,devops@example.com

# Disable email entirely
MAILTO=""

# All jobs below inherit the MAILTO setting
0 2 * * * /home/user/backup.sh

For email to work, your server needs a working mail transfer agent (MTA) such as postfix or sendmail.

Checking the Cron Log

Cron logs every job execution to syslog. This is your first stop when debugging:

# View cron entries in syslog (Debian/Ubuntu)
grep CRON /var/log/syslog

# View cron entries on RHEL/CentOS
grep CRON /var/log/cron

# Follow the log in real time
tail -f /var/log/syslog | grep CRON

A typical log entry looks like:

Jan 24 02:00:01 server CRON[12345]: (user) CMD (/home/user/backup.sh)

This confirms the job was triggered. If the command is missing from the log, the issue is with the crontab syntax or the daemon itself.


Practical Examples

Example 1: Automated Database Backup

# Back up a MySQL database every night at 2:00 AM
0 2 * * * /usr/bin/mysqldump -u dbuser -p'SecurePass123' mydb | /usr/bin/gzip > /backup/mysql/mydb-$(date +\%Y\%m\%d).sql.gz 2>> /var/log/mysql-backup.log

Note: In crontab, you must escape percent signs (%) with a backslash (\%) because cron interprets % as a newline. This is one of the most common cron gotchas.

A more robust approach uses a wrapper script:

#!/bin/bash
# /home/user/scripts/mysql-backup.sh

BACKUP_DIR="/backup/mysql"
DB_NAME="mydb"
DATE=$(date +%Y%m%d_%H%M%S)
LOG="/var/log/mysql-backup.log"

echo "[$DATE] Starting backup of $DB_NAME" >> "$LOG"

/usr/bin/mysqldump -u dbuser -p'SecurePass123' "$DB_NAME" | /usr/bin/gzip > "$BACKUP_DIR/${DB_NAME}-${DATE}.sql.gz"

if [ $? -eq 0 ]; then
    echo "[$DATE] Backup completed successfully" >> "$LOG"
else
    echo "[$DATE] ERROR: Backup failed" >> "$LOG"
fi

# Delete backups older than 30 days
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
# Crontab entry for the wrapper script
0 2 * * * /home/user/scripts/mysql-backup.sh

Example 2: Log Cleanup

# Delete log files older than 7 days every Sunday at 3:00 AM
0 3 * * 0 /usr/bin/find /var/log/myapp -name "*.log" -mtime +7 -delete >> /var/log/cleanup.log 2>&1

# Compress logs older than 1 day, every day at 4:00 AM
0 4 * * * /usr/bin/find /var/log/myapp -name "*.log" -mtime +1 ! -name "*.gz" -exec /usr/bin/gzip {} \; 2>> /var/log/cleanup-errors.log

Example 3: System Monitoring

# Check disk usage every 5 minutes, alert if above 90%
*/5 * * * * /home/user/scripts/disk-check.sh

The monitoring script:

#!/bin/bash
# /home/user/scripts/disk-check.sh

THRESHOLD=90

df -H | awk 'NR>1 {print $5 " " $6}' | while read usage mount; do
    percent=$(echo "$usage" | tr -d '%')
    if [ "$percent" -ge "$THRESHOLD" ]; then
        echo "WARNING: Disk usage on $mount is ${usage}" | \
            /usr/bin/mail -s "Disk Alert: $mount at ${usage}" admin@example.com
    fi
done

Example 4: SSL Certificate Renewal

# Attempt certificate renewal twice daily (Let's Encrypt recommendation)
0 4,16 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx" >> /var/log/certbot-renew.log 2>&1

Example 5: System Updates Check

# Check for available updates every morning at 6:00 AM
0 6 * * * /usr/bin/apt update -qq && /usr/bin/apt list --upgradable 2>/dev/null | /usr/bin/mail -s "Available Updates Report" admin@example.com

Cron Directories

Linux distributions provide a set of directories for scripts that should run at predefined intervals. Simply drop an executable script into the appropriate directory:

DirectoryFrequency
/etc/cron.hourly/Every hour
/etc/cron.daily/Every day
/etc/cron.weekly/Every week
/etc/cron.monthly/Every month
# List all scripts in cron.daily
ls -la /etc/cron.daily/

# Typical output shows logrotate, apt-compat, man-db, etc.

Requirements for Scripts in Cron Directories

  1. The script must be executable: chmod +x /etc/cron.daily/myscript
  2. The script must not have a file extension (.sh, .py, etc.) — run-parts skips files with dots in the name by default on many systems
  3. The script must start with a proper shebang: #!/bin/bash
# Create a daily cleanup script
sudo nano /etc/cron.daily/cleanup-temp

# Add content:
#!/bin/bash
find /tmp -type f -mtime +7 -delete

# Make it executable
sudo chmod +x /etc/cron.daily/cleanup-temp

Note: These directories are managed by anacron on many distributions, which ensures missed jobs run after the system comes back online. This is particularly useful for laptops and desktops that are not running 24/7.


Security Considerations

Controlling Access with cron.allow and cron.deny

You can restrict which users are allowed to create cron jobs:

# /etc/cron.allow -- only listed users can use cron
# If this file exists, ONLY users listed here can use crontab
echo "admin" | sudo tee /etc/cron.allow
echo "deploy" | sudo tee -a /etc/cron.allow

# /etc/cron.deny -- listed users are blocked from using cron
# If cron.allow does not exist, everyone EXCEPT users listed here can use crontab
echo "guest" | sudo tee /etc/cron.deny

The logic is:

  1. If /etc/cron.allow exists, only users listed in it can use cron
  2. If /etc/cron.allow does not exist but /etc/cron.deny exists, everyone except listed users can use cron
  3. If neither file exists, the default depends on the distribution (often only root)

File Permissions

Cron jobs run with the permissions of the user who owns the crontab. Keep your scripts secure:

# Ensure scripts are owned by the correct user and not world-writable
chmod 750 /home/user/scripts/backup.sh
chown user:user /home/user/scripts/backup.sh

# Never store passwords in crontab -- use config files with restricted permissions
chmod 600 /home/user/.my.cnf

Avoid Running Cron Jobs as Root

Whenever possible, create a dedicated service user for cron tasks:

# Create a dedicated backup user
sudo useradd -r -s /usr/sbin/nologin backupuser

# Set up its crontab
sudo crontab -u backupuser -e

Crontab Syntax Quick Reference

ScheduleExpressionDescription
Every minute* * * * *Runs every minute of every day
Every 5 minutes*/5 * * * *Runs at minutes 0, 5, 10, 15, …
Every hour0 * * * *Runs at the top of every hour
Every 6 hours0 */6 * * *Runs at 0:00, 6:00, 12:00, 18:00
Daily at midnight0 0 * * *Runs once a day at 00:00
Daily at 2:30 AM30 2 * * *Runs once a day at 02:30
Every Monday at 9 AM0 9 * * 1Runs weekly on Monday morning
Weekdays at 8 AM0 8 * * 1-5Monday through Friday at 08:00
First of the month0 0 1 * *Midnight on the 1st of each month
Every quarter0 0 1 1,4,7,10 *Midnight on Jan 1, Apr 1, Jul 1, Oct 1
Twice daily0 4,16 * * *Runs at 4:00 AM and 4:00 PM
Every 30 minutes*/30 * * * *Runs at :00 and :30 of every hour
At reboot@rebootRuns once when the system starts

Troubleshooting

Problem: Cron Job Runs in Terminal but Not in Cron

Cause: PATH differences. Your interactive shell has a rich PATH; cron does not.

Solution: Use absolute paths for all commands, or set PATH at the top of your crontab:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Problem: Percent Signs Breaking Commands

Cause: Cron interprets % as a newline character.

Solution: Escape all percent signs with a backslash:

# Wrong
0 2 * * * /usr/bin/date +%Y%m%d

# Correct
0 2 * * * /usr/bin/date +\%Y\%m\%d

Problem: Permission Denied Errors

Cause: The script is not executable or the user does not have access.

Solution:

# Make the script executable
chmod +x /home/user/scripts/myscript.sh

# Verify ownership
ls -la /home/user/scripts/myscript.sh

Problem: Timezone Issues

Cause: Cron uses the system timezone by default. If your server is set to UTC but you expect local time, jobs run at unexpected hours.

Solution:

# Check system timezone
timedatectl

# Set timezone
sudo timedatectl set-timezone America/New_York

# Or set TZ in crontab for per-job timezone
TZ=America/New_York
0 9 * * * /home/user/morning-report.sh

Problem: Job Runs but Produces No Output

Cause: Output is being sent to local mail, which no one reads, or output is discarded.

Solution: Redirect output to a log file:

0 2 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1

Problem: Cron Daemon Not Running

Solution:

# Check status
sudo systemctl status cron

# Start it
sudo systemctl start cron

# Enable at boot
sudo systemctl enable cron

Summary

Cron is the backbone of automation on Linux. With five simple time fields and a command, you can schedule virtually any task to run at any interval. The key points to remember are:

  • Always use absolute paths for commands in cron jobs
  • Escape percent signs (\%) in crontab entries
  • Redirect output to log files for debugging and auditing
  • Set environment variables (especially PATH) at the top of your crontab
  • Back up your crontab before making changes
  • Check syslog (grep CRON /var/log/syslog) when a job does not run as expected
  • Restrict access with /etc/cron.allow and /etc/cron.deny

Cron works hand in hand with other server administration practices. For securing the server that runs your cron jobs, see the Linux Server Security Checklist and SSH Hardening: 12 Steps to Secure Your Linux Server.