"crontab -r" is the most dangerous keystroke combination in Unix. One letter from the harmless -l, it deletes your entire crontab with no confirmation. Combine that with the fact that crontabs are routinely lost during server migrations, distro upgrades, and accidental "let me re-run that interactive setup script," and you have a compelling case for backing up scheduled jobs as a routine, automated practice.
Where crontabs actually live
Three locations on most distributions:
/var/spool/cron/crontabs/โ per-user crontabs (Debian/Ubuntu)./var/spool/cron/โ per-user crontabs (RHEL family)./etc/crontaband/etc/cron.d/โ system-wide schedules./etc/cron.{hourly,daily,weekly,monthly}/โ drop-in scripts.
Backing up "the crontab" means capturing all five locations, plus per-user copies for every UID that has one.
The one-line backup
sudo bash -c '
ts=$(date +%F)
out=/var/backups/cron/$ts
mkdir -p "$out"
cp -a /var/spool/cron "$out/spool" 2>/dev/null
cp -a /etc/crontab "$out/etc-crontab" 2>/dev/null
cp -a /etc/cron.d "$out/etc-cron.d" 2>/dev/null
cp -a /etc/cron.hourly /etc/cron.daily \
/etc/cron.weekly /etc/cron.monthly "$out/" 2>/dev/null
for u in $(awk -F: "\$3 >= 1000 {print \$1}" /etc/passwd) root; do
crontab -u "$u" -l 2>/dev/null > "$out/user-$u.cron"
[ -s "$out/user-$u.cron" ] || rm -f "$out/user-$u.cron"
done
tar czf "$out.tar.gz" -C /var/backups/cron "$ts"
rm -rf "$out"'
Drop the script into /etc/cron.daily/backup-cron and let cron back itself up. Retention via find /var/backups/cron -name '*.tar.gz' -mtime +30 -delete.
Per-user export
crontab -l # show current
crontab -l > ~/cron-backup-$(date +%F).txt # save
crontab ~/cron-backup-2026-04-15.txt # restore exact contents
Always pipe crontab -l output to a file before any maintenance โ the second of insurance is worth it.
Diff: catching unauthorized changes
A crontab change you did not make is almost always either an attacker establishing persistence or a misbehaving auto-config tool. Detect with diff:
sudo crontab -u root -l > /tmp/cron-now.txt
sudo diff /var/backups/cron/baseline-root.txt /tmp/cron-now.txt
Wrap into a daily check that emails or alerts on any non-empty diff. Persistent attackers love cron because it survives reboot, runs as root, and hides in plain sight.
Restoring after disaster
Two common scenarios:
Scenario A: crontab -r by accident.
crontab ~/cron-backup-2026-04-15.txt
crontab -l # verify
Scenario B: full server rebuild.
tar xzf /backup/cron-2026-04-15.tar.gz -C /tmp
sudo cp /tmp/2026-04-15/etc-crontab /etc/crontab
sudo cp -a /tmp/2026-04-15/etc-cron.d/. /etc/cron.d/
for f in /tmp/2026-04-15/user-*.cron; do
user=$(basename "$f" .cron); user=${user#user-}
sudo crontab -u "$user" "$f"
done
sudo systemctl restart cron
Validating cron syntax
Cron silently ignores invalid lines, which is the worst possible failure mode. Validate before deploying:
crontab -T crontab.txt # vixie-cron 3.0pl1+
# or:
sudo cat < crontab.txt >> /var/spool/cron/crontabs/testuser
journalctl -u cron --since '5 minutes ago' | grep 'CRON.*ERROR'
For complex schedules, use crontab.guru to translate the expression to English. 0 9 * * 1-5 means "09:00 Monday through Friday" โ easy to typo into something else entirely.
Migrating to systemd timers
If you are already operating systemd units, consider converting cron jobs to timers (covered in our systemd-timers article). One automated path: run systemd-analyze calendar 'OnCalendar=...' on each candidate to validate the equivalent expression before cutover.
Best practices
- Always set
MAILTO=ops@example.comat the top of the crontab. Cron emails errors; if you do not collect them, you do not know about failures. - Use absolute paths for binaries and data files; cron runs with a near-empty PATH.
- Redirect both stdout and stderr explicitly:
0 3 * * * /usr/local/bin/job >> /var/log/job.log 2>&1. - Avoid clustering many jobs at the top of the hour โ stagger them across the hour to reduce contention.
- Annotate jobs with comments so the next admin understands why each exists.
Common pitfalls
crontab -rwith no backup; typecrontab -i -rinstead, which prompts for confirmation.- Editing crontab on the wrong host because the prompt did not show the hostname.
- Forgetting that environment variables in your interactive shell are not present in cron's environment.
- Putting
%in commands without escaping โ cron treats%as a newline.
Backing up crontabs is a five-line script and a single daily timer slot. Run it on every server you administer and you will never again hear "the cron job that does X used to exist; nobody knows what it did."