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
sudoprivileges - Basic familiarity with the command line and a text editor (
nanoorvim)
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
| Operator | Meaning | Example |
|---|---|---|
* | Any value | * * * * * = every minute |
, | List of values | 1,15,30 * * * * = minutes 1, 15, 30 |
- | Range of values | 1-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:
| String | Equivalent | Description |
|---|---|---|
@reboot | N/A | Run once at startup |
@yearly | 0 0 1 1 * | Once a year (Jan 1, midnight) |
@monthly | 0 0 1 * * | Once a month (1st, midnight) |
@weekly | 0 0 * * 0 | Once a week (Sunday, midnight) |
@daily | 0 0 * * * | Once a day (midnight) |
@hourly | 0 * * * * | 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 -rdeletes all your cron jobs immediately with no confirmation and no undo. If you only want to remove one entry, usecrontab -eand 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:
| Directory | Frequency |
|---|---|
/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
- The script must be executable:
chmod +x /etc/cron.daily/myscript - The script must not have a file extension (
.sh,.py, etc.) —run-partsskips files with dots in the name by default on many systems - 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
anacronon 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:
- If
/etc/cron.allowexists, only users listed in it can use cron - If
/etc/cron.allowdoes not exist but/etc/cron.denyexists, everyone except listed users can use cron - 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
| Schedule | Expression | Description |
|---|---|---|
| Every minute | * * * * * | Runs every minute of every day |
| Every 5 minutes | */5 * * * * | Runs at minutes 0, 5, 10, 15, … |
| Every hour | 0 * * * * | Runs at the top of every hour |
| Every 6 hours | 0 */6 * * * | Runs at 0:00, 6:00, 12:00, 18:00 |
| Daily at midnight | 0 0 * * * | Runs once a day at 00:00 |
| Daily at 2:30 AM | 30 2 * * * | Runs once a day at 02:30 |
| Every Monday at 9 AM | 0 9 * * 1 | Runs weekly on Monday morning |
| Weekdays at 8 AM | 0 8 * * 1-5 | Monday through Friday at 08:00 |
| First of the month | 0 0 1 * * | Midnight on the 1st of each month |
| Every quarter | 0 0 1 1,4,7,10 * | Midnight on Jan 1, Apr 1, Jul 1, Oct 1 |
| Twice daily | 0 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 | @reboot | Runs 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.allowand/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.