Cron is everywhere and fine. Timers are better at exactly one thing that matters at 3am: telling you what happened. Do both, then decide with evidence.
1. A job worth scheduling
sudo tee /usr/local/bin/diskreport.sh >/dev/null <<'SCRIPT'
#!/bin/bash
set -euo pipefail
USED=$(df --output=pcent / | tail -n 1 | tr -dc '0-9')
echo "root filesystem ${USED}% used"
[[ $USED -lt 90 ]] || { echo "over threshold" >&2; exit 1; }
SCRIPT
sudo chmod 755 /usr/local/bin/diskreport.sh
/usr/local/bin/diskreport.sh; echo "exit=$?"It prints a line and exits non-zero over 90%. Both matter: a scheduler needs an exit status to decide whether something failed.
Verify
2. Schedule it with cron
sudo tee /etc/cron.d/diskreport >/dev/null <<'CRON'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * root /usr/local/bin/diskreport.sh
CRON
sudo chmod 644 /etc/cron.d/diskreport
sudo systemctl restart cron 2>/dev/null || sudo systemctl restart crondTwo things bite people here. A file in /etc/cron.d needs a user field (root above) that a personal crontab does not, and it must not be world-writable or cron ignores it. And cron runs with a nearly empty environment — setting PATH explicitly is not optional, because the script that works in your shell fails under cron for want of it.
Verify
3. Schedule it with a timer
sudo tee /etc/systemd/system/diskreport.service >/dev/null <<'UNIT'
[Unit]
Description=Disk usage report
[Service]
Type=oneshot
ExecStart=/usr/local/bin/diskreport.sh
UNIT
sudo tee /etc/systemd/system/diskreport.timer >/dev/null <<'UNIT'
[Unit]
Description=Run the disk usage report every 5 minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
AccuracySec=30s
RandomizedDelaySec=15s
[Install]
WantedBy=timers.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now diskreport.timer
systemctl list-timers diskreport.timer --no-pagerA timer is two units: a .service describing the work as Type=oneshot, and a .timer describing when. Four directives with no cron equivalent:
Persistent=trueruns a missed job after downtime. Cron simply skips it.AccuracySeclets systemd batch wakeups instead of firing on the second, which matters on a laptop or a dense host.RandomizedDelaySecjitters the start, so a hundred hosts do not hammer the same endpoint at exactly midnight.OnCalendartakes readable expressions:daily,Mon..Fri 09:00,*:0/5.
Verify
4. Check the calendar expression before trusting it
systemd-analyze calendar "*:0/5"
systemd-analyze calendar "Mon..Fri 09:00"
systemd-analyze calendar "daily"This prints the normalised form and the next few firings. There is no cron equivalent — the standard way to check a cron expression is to wait and see, which is how a job meant for 2am ran every minute for a week.
Verify
5. Now compare what each tells you after a failure
Force the job to fail:
sudo tee /usr/local/bin/diskreport.sh >/dev/null <<'SCRIPT'
#!/bin/bash
set -euo pipefail
echo "about to fail"
exit 3
SCRIPT
sudo chmod 755 /usr/local/bin/diskreport.sh
sudo systemctl start diskreport.serviceThe timer's side:
systemctl status diskreport.service --no-pager | head -n 8
journalctl -u diskreport.service -n 15 --no-pager
systemctl show diskreport.service -p ExecMainStatus --valueYou get the exit status, the output, a timestamp, and a unit state you can alert on. Cron's side, by contrast, mails output to the local user if a mail transport exists — and on a cloud instance one usually does not, so the output goes nowhere:
journalctl -t CRON -n 10 --no-pager | tail -n 5
ls -l /var/mail/root 2>/dev/null || echo "no local mail spool — cron output is lost"That is the whole argument. Cron schedules reliably and reports badly; a timer's failure is a queryable unit state.
Verify
Which to use
Use a timer when the job matters: you want the exit status, the output in the journal, Persistent catch-up, and jitter across a fleet. Use cron when the box has no systemd, when the job is trivial and self-logging, or when a runbook you cannot change already says cron. Do not run both for the same job, which this lab did only to compare them.
Clean up
sudo systemctl disable --now diskreport.timer
sudo rm -f /etc/systemd/system/diskreport.timer \
/etc/systemd/system/diskreport.service
sudo rm -f /etc/cron.d/diskreport /usr/local/bin/diskreport.sh
sudo systemctl daemon-reload
sudo systemctl restart cron 2>/dev/null || sudo systemctl restart crondWhere this goes next
You can schedule work and find out how it went. The cloud and DevOps group is next, and it opens with the thing scheduled jobs most often exist to do: a backup you have actually restored.