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 find node or docker. Fix: use absolute paths, or set PATH= at the top of the crontab.
  • Relative paths. Cron starts in the user's home directory, not where the script lives. Always cd to 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

Example · bash
# --- 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.

Example · bash
# 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.txt

Create a systemd timer

Defines a systemd timer with OnCalendar for human-readable scheduling and Persistent to catch up missed runs.

Example · bash
# /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.timer

List and inspect timers

Shows how to inspect, log, and manually run a systemd timer-driven service, unlike cron which has no built-in logging.

Example · bash
# 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

  • Be the first to comment on this lesson.