⏰ Cron Deep Dive

Cron & Scheduling Series: Part 1 — Cron Deep Dive  |  Part 2 — Systemd Timers  |  Part 3 — at & anacron

What is cron?

Cron is the classic Unix job scheduler — a daemon that wakes up every minute, checks its tables, and runs commands whose time has come. It has been part of Unix since the 1970s and remains the backbone of scheduled automation on Linux servers worldwide. Understanding cron thoroughly — syntax, user vs system cron, environment gotchas, and logging — is a core sysadmin skill.

The cron daemon is crond on RHEL-family systems and cron on Debian/Ubuntu. Both read the same crontab format.

Crontab Field Syntax

* * * * * command to run │ │ │ │ └── Day of Week (0-7, 0 and 7 = Sunday, or Sun Mon Tue Wed Thu Fri Sat) │ │ │ └───── Month (1-12, or Jan Feb Mar ... Dec) │ │ └──────── Day of Month (1-31) │ └─────────── Hour (0-23) └────────────── Minute (0-59)

Field Operators

OperatorMeaningExampleResult
*Every value* in minuteEvery minute
,List of values1,15,30 in minuteAt minutes 1, 15, and 30
-Range of values9-17 in hourHours 9 through 17
/Step/interval*/15 in minuteEvery 15 minutes
*/2Every Nth*/2 in hourEvery 2 hours
1-5/2Range with step1-5/2 in hourHours 1, 3, 5

Special Strings (shortcuts)

StringEquivalentMeaning
@rebootRun once at system startup
@yearly0 0 1 1 *Once a year, Jan 1 midnight
@annually0 0 1 1 *Same as @yearly
@monthly0 0 1 * *First day of each month, midnight
@weekly0 0 * * 0Every Sunday at midnight
@daily0 0 * * *Every day at midnight
@midnight0 0 * * *Same as @daily
@hourly0 * * * *Every hour on the hour

Examples

1
Basic crontab Management
# Edit YOUR crontab (opens in $EDITOR)
crontab -e

# List your current crontab
crontab -l

# Remove your crontab entirely
crontab -r

# Edit another user's crontab (as root)
sudo crontab -e -u alice
sudo crontab -l -u alice
sudo crontab -r -u alice

# Install a crontab from a file
crontab mycrontab.txt

# Backup your crontab
crontab -l > ~/mycrontab.bak

# Check cron daemon status
sudo systemctl status crond    # RHEL/Rocky
sudo systemctl status cron     # Debian/Ubuntu
⚠️ crontab -r with no confirmation. Unlike most destructive commands, crontab -r does not ask "are you sure?" — it silently deletes your entire crontab immediately. Always backup first with crontab -l > backup.txt. An easy typo is -r when you meant -e.
2
Common Cron Schedule Examples
# Run every minute (useful for testing — remove after!)
* * * * * /usr/local/bin/myscript.sh

# Every 5 minutes
*/5 * * * * /usr/local/bin/check-service.sh

# Every day at 2:30 AM
30 2 * * * /usr/local/bin/nightly-backup.sh

# Every weekday (Mon-Fri) at 8 AM
0 8 * * 1-5 /usr/local/bin/send-report.sh

# Every Monday at 6 AM
0 6 * * 1 /usr/local/bin/weekly-cleanup.sh

# First day of every month at midnight
0 0 1 * * /usr/local/bin/monthly-report.sh

# Twice a day — 6 AM and 6 PM
0 6,18 * * * /usr/local/bin/sync-data.sh

# Every 15 minutes during business hours (8 AM - 6 PM)
*/15 8-18 * * 1-5 /usr/local/bin/poll-queue.sh

# Once a year — Jan 1 at midnight
@yearly /usr/local/bin/annual-cleanup.sh

# At every reboot
@reboot /usr/local/bin/startup-tasks.sh
💡 Test your cron syntax: Use crontab.guru to verify your schedule expressions before deploying. It shows plain English descriptions and the next scheduled run times.
3
User Crontab vs System Cron Files

Cron has multiple layers — knowing which one to use matters:

# USER CRONTABS — managed with crontab -e
# Stored in /var/spool/cron/crontabs/ (per user)
# Format: minute hour dom month dow command
# Run as the user who owns the crontab

# SYSTEM CRONTAB — /etc/crontab
# Has an extra USERNAME field:
# minute hour dom month dow USERNAME command
cat /etc/crontab
/etc/crontab format:
SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=root # minute hour dom month dow user command 01 * * * * root run-parts /etc/cron.hourly 02 4 * * * root run-parts /etc/cron.daily 22 4 * * 0 root run-parts /etc/cron.weekly 42 4 1 * * root run-parts /etc/cron.monthly
# /etc/cron.d/ — drop-in cron files (same format as /etc/crontab)
# Best place for package and system-level scheduled jobs
ls /etc/cron.d/

# /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/
# Drop executable scripts here — run by run-parts at the time above
ls /etc/cron.daily/
ls /etc/cron.weekly/
Which to use?
4
The Cron Environment Problem

The most common reason cron jobs fail when run manually they work fine — cron runs with a minimal environment:

# Cron's environment is minimal — very different from your shell
# Check what cron sees by logging it
* * * * * env > /tmp/cron-environment.txt

# Typical cron environment:
# HOME=/root
# LOGNAME=root
# PATH=/usr/bin:/bin   <-- much shorter than your interactive PATH
# SHELL=/bin/sh        <-- sh, not bash!

# SOLUTIONS:

# 1. Set PATH at top of crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 2 * * * myscript.sh

# 2. Use full paths in cron commands (most reliable)
30 2 * * * /usr/local/bin/myscript.sh

# 3. Set SHELL explicitly
SHELL=/bin/bash
30 2 * * * myscript.sh

# 4. Source your profile inside the script
# #!/bin/bash
# source /etc/profile
# source ~/.bash_profile
⚠️ Always use full paths in cron. Your interactive shell has /usr/local/bin in PATH — cron may not. Commands like python3, node, or custom scripts in /usr/local/bin will fail with "command not found" in cron unless you use the full path or set PATH explicitly.
5
Cron Output and Email — MAILTO
# By default cron emails output to the user running the job
# Set MAILTO to redirect or suppress

# Send output to a specific address
MAILTO=sysadmin@example.com
30 2 * * * /usr/local/bin/backup.sh

# Suppress all email (discard stdout and stderr)
MAILTO=""
30 2 * * * /usr/local/bin/backup.sh

# Redirect output to a log file instead
30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Log with timestamp
30 2 * * * echo "$(date): Starting backup" >> /var/log/backup.log; \
           /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Send errors to log but discard normal output
30 2 * * * /usr/local/bin/backup.sh > /dev/null 2>> /var/log/backup-errors.log
💡 Always log cron output. A cron job that produces no output and no log is invisible when it fails. At minimum redirect stderr to a log file. For important jobs redirect both stdout and stderr and rotate the log with logrotate.
6
Writing a Production Cron Job Script
#!/bin/bash
# nightly-backup.sh — example production cron script

# Use full paths — cron has a minimal PATH
RSYNC=/usr/bin/rsync
LOGGER=/usr/bin/logger
DATE=$(date '+%Y-%m-%d %H:%M:%S')

# Log function — writes to both syslog and log file
log() {
    echo "[$DATE] $*" | tee -a /var/log/nightly-backup.log
    $LOGGER -t nightly-backup "$*"
}

# Lock file — prevent overlapping runs
LOCKFILE=/var/run/nightly-backup.lock
if [ -f "$LOCKFILE" ]; then
    log "ERROR: Already running (lockfile exists). Exiting."
    exit 1
fi
touch "$LOCKFILE"
trap 'rm -f "$LOCKFILE"' EXIT

log "Starting nightly backup"

# Do the work
$RSYNC -av --delete /data/ /backup/nightly/ >> /var/log/nightly-backup.log 2>&1
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
    log "Backup completed successfully"
else
    log "ERROR: Backup failed with exit code $EXIT_CODE"
    exit $EXIT_CODE
fi
Production cron script checklist:
7
System Drop-in Jobs — /etc/cron.d/
# /etc/cron.d/ files use the same format as /etc/crontab
# (includes the username field)

# Create a system cron job for log rotation check
sudo tee /etc/cron.d/logcheck << 'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=sysadmin@example.com

# minute hour dom month dow user  command
0       6    *   *   *    root  /usr/sbin/logcheck
EOF

# Correct permissions — cron.d files must not be world-writable
sudo chmod 644 /etc/cron.d/logcheck
sudo chown root:root /etc/cron.d/logcheck

# Verify cron picks it up (no restart needed)
sudo systemctl status crond
sudo ls -la /etc/cron.d/
cron.d file permissions matter: Cron will silently ignore files in /etc/cron.d/ that are world-writable or not owned by root. Always set chmod 644 and chown root:root on files you drop there.
8
Access Control — cron.allow and cron.deny
# Control which users can use crontab

# /etc/cron.allow — whitelist (if exists, ONLY these users can use cron)
sudo tee /etc/cron.allow << 'EOF'
root
alice
bob
EOF

# /etc/cron.deny — blacklist (these users cannot use cron)
sudo tee /etc/cron.deny << 'EOF'
tempuser
contractor1
EOF

# Logic:
# cron.allow exists → only listed users can use cron
# cron.allow absent, cron.deny exists → everyone except listed users
# Neither file exists → only root can use cron (RHEL default)
# Both empty → no one can use cron

# List all user crontabs on the system
sudo ls -la /var/spool/cron/
sudo ls -la /var/spool/cron/crontabs/   # Debian/Ubuntu path

Quick Reference

CommandWhat it does
crontab -eEdit your crontab
crontab -lList your crontab
crontab -rRemove your crontab (no confirmation!)
crontab -e -u userEdit another user's crontab (root)
crontab -l > backup.txtBackup crontab to file
systemctl status crondCheck cron daemon (RHEL)
systemctl status cronCheck cron daemon (Debian)
ls /etc/cron.d/List system drop-in cron jobs
ls /etc/cron.daily/List daily run-parts scripts
run-parts /etc/cron.dailyManually run daily cron scripts
journalctl -u crondView cron daemon logs (RHEL)
grep CRON /var/log/syslogView cron logs (Debian)

Cron Locations Summary

LocationPurposeFormat
/var/spool/cron/User crontabs5 fields + command
/etc/crontabSystem crontab5 fields + user + command
/etc/cron.d/System drop-in jobs5 fields + user + command
/etc/cron.hourly/Scripts run every hourExecutable script
/etc/cron.daily/Scripts run dailyExecutable script
/etc/cron.weekly/Scripts run weeklyExecutable script
/etc/cron.monthly/Scripts run monthlyExecutable script