Scheduling: cron Gotchas & systemd Timers
Avoid the classic cron traps, and use systemd timers when you need logging, retries and dependencies.
Cron is wonderfully simple and quietly treacherous. Most "my cron job doesn't run" tickets come down to two things: environment and paths.
The two cron gotchas
- Minimal environment. Cron runs with a bare
PATH(often just/usr/bin:/bin) and none of your~/.bashrc. A script that works in your shell fails under cron because it can't findnodeordocker. Fix: use absolute paths, or setPATH=at the top of the crontab. - Relative paths. Cron starts in the user's home directory, not where the script lives. Always
cdto a known absolute directory, or reference files absolutely.
Also redirect output — cron mails stdout/stderr to a local mailbox nobody reads. Append >> /var/log/myjob.log 2>&1 so you can actually see what happened.
When to graduate to systemd timers
Timers are more verbose to set up but give you what cron cannot: output captured in the journal automatically, OnFailure= hooks, dependency ordering, randomised delays (RandomizedDelaySec), Persistent=true to run a missed job after downtime, and systemctl list-timers to see the next run time at a glance.
Example
# --- Cron done right: explicit PATH, absolute paths, logging ---
# crontab -e
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * cd /srv/app && /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# --- The systemd timer equivalent ---
# /etc/systemd/system/backup.service
# [Service]
# Type=oneshot
# ExecStart=/srv/app/backup.sh
#
# /etc/systemd/system/backup.timer
# [Timer]
# OnCalendar=*-*-* 02:00:00
# Persistent=true # run on next boot if 02:00 was missed
# RandomizedDelaySec=300
#
# [Install]
# WantedBy=timers.target
sudo systemctl enable --now backup.timer
systemctl list-timers --all # when does each timer fire next?When to use it
- A sysadmin replaces a fragile cron job with a systemd timer so the task's output is captured in journald and failures trigger automatic retries.
- A DevOps engineer uses a systemd OnCalendar timer to run a weekly backup at 02:00 every Sunday, with logs viewable via journalctl.
- A developer avoids cron's PATH pitfalls by specifying the full binary path in a systemd ExecStart, ensuring the correct interpreter is used.
More examples
Avoid cron PATH gotchas
Shows that cron runs with a minimal PATH; use absolute paths and redirect both stdout/stderr to a log file.
# BAD: relies on cron's minimal PATH
# 0 2 * * * mybackup.sh
# GOOD: always use absolute paths in cron
0 2 * * * /usr/local/bin/mybackup.sh >> /var/log/mybackup.log 2>&1
# Debug: print PATH in a cron job
* * * * * env > /tmp/cron_env.txtCreate a systemd timer
Defines a systemd timer with OnCalendar for human-readable scheduling and Persistent to catch up missed runs.
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
# Enable and start
sudo systemctl enable --now backup.timerList and inspect timers
Shows how to inspect, log, and manually run a systemd timer-driven service, unlike cron which has no built-in logging.
# List all active timers with next/last run times
systemctl list-timers --all
# Check timer logs
journalctl -u backup.service
# Manually trigger the associated service
sudo systemctl start backup.service
Discussion