Operations 43 min read

8 Crontab Pitfalls Every SRE Should Avoid – Proven Fixes & Best Practices

Learn from a seasoned SRE’s hard‑won experience as we dissect eight common crontab pitfalls—environment variables, permissions, time zones, email spam, path issues, concurrency, logging, and special character quirks—and provide concrete solutions, best‑practice configurations, monitoring tips, and migration guidance to systemd timers.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
8 Crontab Pitfalls Every SRE Should Avoid – Proven Fixes & Best Practices

Overview

crontab (cron table) is the de‑facto scheduler on Unix‑like systems since 1975. It runs commands at specified times, acting like an alarm manager. It is lightweight, reliable, user‑isolated and logs each execution.

Environment requirements

Supported operating systems: CentOS 7/8, Rocky Linux 8/9, Ubuntu 20.04/22.04. Cron versions: cronie 1.5.x or vixie‑cron. Shell: Bash 4.x+.

# CentOS/RHEL
systemctl status crond
# Ubuntu/Debian
systemctl status cron

If the service is not active, start and enable it:

# CentOS/RHEL
sudo systemctl start crond
sudo systemctl enable crond
# Ubuntu/Debian
sudo systemctl start cron
sudo systemctl enable cron

Preparation

System time & timezone – verify with date and timedatectl. Ensure NTP sync and restart the cron daemon after any timezone change.

Crontab file locations – system‑wide files are under /etc/crontab, /etc/cron.d/, /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, /etc/cron.monthly/. User‑specific files reside in /var/spool/cron/ (CentOS/RHEL) or /var/spool/cron/crontabs/ (Ubuntu/Debian). Most users edit their own crontab with crontab -e.

User permissions – controlled by /etc/cron.allow (whitelist) and /etc/cron.deny (blacklist). If cron.allow exists, only listed users may use cron; otherwise the deny list applies.

Core configuration

Time syntax

# * * * * * command
# minute hour day month weekday
# Special characters:
#   * – any value
#   , – list (e.g., 1,15,30)
#   - – range (e.g., 9-18)
#   / – step (e.g., */5 for every five minutes)

Common examples:

# Every minute
* * * * * /path/to/script.sh
# Every 5 minutes
*/5 * * * * /path/to/script.sh
# At 02:00 daily
0 2 * * * /path/to/script.sh
# At 09:00 and 18:00 on weekdays
0 9,18 * * 1-5 /path/to/script.sh
# Every Monday at 03:00
0 3 * * 1 /path/to/script.sh

Editing crontab

# Edit current user
crontab -e
# Edit root
sudo crontab -e
# Edit another user
sudo crontab -u username -e

Common crontab commands

# List current crontab
crontab -l
# Remove all entries (dangerous!)
crontab -r
# Remove with confirmation
crontab -ri
# Load from a file
crontab /path/to/file

Sample production crontab

# ============================================
# Crontab Configuration for Production Server
# ============================================

# Global environment (recommended)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
HOME=/home/deploy

# ---------- System Maintenance ----------
# Daily log cleanup (03:00)
0 3 * * * /usr/bin/flock -xn /tmp/log_cleanup.lock /home/deploy/scripts/log_cleanup.sh >> /var/log/cron/log_cleanup.log 2>&1
# Daily temporary file cleanup (04:00)
0 4 * * * /usr/bin/flock -xn /tmp/tmp_cleanup.lock /usr/bin/find /tmp -type f -mtime +3 -delete >> /var/log/cron/tmp_cleanup.log 2>&1

# ---------- Database Backups ----------
# MySQL full backup daily at 02:00
0 2 * * * /usr/bin/flock -xn /tmp/mysql_backup.lock /home/deploy/scripts/mysql_backup.sh >> /var/log/backup/mysql_backup.log 2>&1

# ---------- Application Monitoring ----------
# Health check every minute
* * * * * /home/deploy/scripts/health_check.sh >> /var/log/monitor/health_check.log 2>&1

# ---------- Business‑Critical Tasks ----------
# Daily report email (08:00 weekdays)
0 8 * * 1-5 /usr/bin/flock -xn /tmp/daily_report.lock /home/deploy/scripts/send_daily_report.sh >> /var/log/report/daily.log 2>&1

Common pitfalls and solutions

Missing environment variables (PATH) – crontab runs with a minimal PATH (/usr/bin:/bin). Define a full PATH at the top of the crontab and/or use absolute paths inside scripts. Example:

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

.

Permission issues – ensure scripts are executable ( chmod +x /path/to/script.sh), that the user can read/write required directories, and that the user is allowed to use cron (check /etc/cron.allow / /etc/cron.deny). For privileged commands, use root’s crontab or configure password‑less sudo for specific commands.

Timezone mismatch – crontab uses the system timezone. Verify with timedatectl, set the correct zone (e.g., sudo timedatectl set-timezone Asia/Shanghai) and restart the cron daemon. Optionally set TZ=Asia/Shanghai inside the crontab if supported.

Unwanted email spam – by default cron emails stdout/stderr to the user. Disable it with MAILTO="" at the top of the crontab or redirect output explicitly (e.g., > /var/log/cron/job.log 2>&1).

Relative script paths – crontab’s working directory is the user’s home. Use absolute paths or change directory in the crontab line:

0 2 * * * cd /home/deploy/scripts && ./backup.sh >> /var/log/cron/backup.log 2>&1

. Inside scripts, obtain the script directory with cd "$(dirname "${BASH_SOURCE[0]}")" before using relative files.

Concurrent executions – if a job may run longer than its interval, protect it with flock. Example:

* * * * * /usr/bin/flock -xn /tmp/job.lock /home/deploy/scripts/job.sh >> /var/log/cron/job.log 2>&1

.

Log management – redirect both stdout and stderr to dedicated log files, include timestamps, and configure logrotate to prevent disk exhaustion. Example logrotate snippet:

/var/log/cron/*.log {
    daily
    rotate 14
    compress
    missingok
    notifempty
    create 0640 deploy deploy
}

Special character (%) – in crontab, % denotes a newline and feeds the remainder to the command’s stdin. Escape a literal percent as \% or move the logic into a wrapper script.

Troubleshooting checklist

Confirm the cron daemon is running ( systemctl status crond or systemctl status cron).

Validate crontab syntax with crontab -l and online tools such as crontab.guru.

Inspect system logs: journalctl -u crond -f (CentOS) or journalctl -u cron -f (Ubuntu).

Run the script manually in a clean environment:

env -i PATH=/usr/bin:/bin HOME=$HOME /bin/bash /path/to/script.sh

.

Check file and directory permissions.

Verify the user is allowed via /etc/cron.allow / /etc/cron.deny.

systemd‑timer vs crontab (2025 recommendation)

systemd‑timer offers second‑level precision, dependency management, integrated journald logging, automatic retries, resource limits, persistent execution after downtime, random delays, and sandboxing options. Use it for new projects that need any of these features. Existing stable crontab setups can remain unchanged.

Example systemd service and timer

# /etc/systemd/system/backup.service
[Unit]
Description=Daily MySQL backup
After=network.target mysql.service

[Service]
Type=oneshot
User=deploy
ExecStart=/home/deploy/scripts/mysql_backup.sh
StandardOutput=journal
StandardError=journal
MemoryMax=1G
CPUQuota=50%
ProtectSystem=strict
PrivateTmp=true

# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 02:00

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=1800

[Install]
WantedBy=timers.target

Enable and start the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
sudo systemctl status backup.timer

Best practices

Define SHELL, PATH, and MAILTO at the top of every crontab.

Redirect both stdout and stderr to dedicated log files; include timestamps in script logs.

Use flock for jobs that must not overlap.

Configure logrotate for all cron‑generated logs.

Back up crontab regularly, e.g., crontab -l > /backup/crontab_$(date +%F).bak.

For new workloads that require finer control, prefer systemd‑timer over crontab.

Reference commands

# Crontab management
crontab -l
crontab -e
crontab -r
crontab -ri
crontab /path/to/file
sudo crontab -u user -l

# Service control
systemctl status crond   # CentOS/RHEL
systemctl status cron    # Ubuntu/Debian
systemctl restart crond
systemctl restart cron

# Log inspection
journalctl -u crond -f
journalctl -u cron -f
grep CRON /var/log/cron
grep CRON /var/log/syslog

# File lock
flock -xn /tmp/job.lock command
flock -xn -w 10 /tmp/job.lock command

# Simulate cron environment
env -i PATH=/usr/bin:/bin HOME=$HOME /bin/bash /path/to/script.sh

Further reading

man 5 crontab

man 8 crond

man 5 systemd.timer

crontab.guru (online expression validator)

https://healthchecks.io (cron monitoring service)

MonitoringAutomationOpsschedulingcrontabsystemd
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.