PIPE&GREP(1)

NAME

systemd timers or cron

A nightly backup written both ways, the units and the commands to manage them, and where each one is the better tool.

SYNOPSIS

/ 3 min read / notes(1)

DESCRIPTION

My backups ran from a crontab line for years. Then one morning the laptop had been asleep at 03:00, the job never ran, and nothing told me. Now I use systemd timers for anything I would miss, and I still use cron for throwaway jobs. Here is what each looks like.

The cron version

0 3 * * * /home/you/bin/backup.sh >> /home/you/.local/state/backup.log 2>&1

Five time fields, a command, and a redirect. That is its appeal. Cron keeps no record of whether the job ran, and it runs with a minimal environment, so anything you take for granted in your shell (PATH, aliases, an ssh-agent socket) is missing. Edit the table with crontab -e.

The systemd version

You need two files. The service says what to run, and the timer says when.

~/.config/systemd/user/backup.service:

[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=%h/bin/backup.sh

~/.config/systemd/user/backup.timer:

[Unit]
Description=Run the nightly backup at 03:00

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=15min

[Install]
WantedBy=timers.target

%h expands to your home directory. Persistent=true means that if the machine was off at 03:00, the timer fires once as soon as it is up again. RandomizedDelaySec adds a random delay of up to 15 minutes to each start. The service has no [Install] section because the timer is the unit you enable.

For a system-wide setup, put the same files in /etc/systemd/system, add User= under [Service], use an absolute path in ExecStart, and drop --user from the commands below.

Load the units and start the timer:

systemctl --user daemon-reload
systemctl --user enable --now backup.timer
systemctl --user list-timers

To test without waiting for 3 a.m., run the service by hand and read the log:

systemctl --user start backup.service
journalctl --user -u backup.service -n 50 --no-pager

Before trusting a calendar expression, check when it actually fires:

systemd-analyze calendar "Mon..Fri 09:00"

It prints the normalized form and the next elapse time, which is the quickest way to catch a weekday range that does not mean what you thought.

The honest tradeoffs

Logging is the biggest win. Every run goes to the journal with start and stop times and an exit status, and systemctl --user status backup.service shows the last result. Cron mails output only if a mailer is configured. Otherwise the output is gone.

Missed runs are handled better, not perfectly. Persistent=true catches up after downtime, which cron does not do. But the catch-up run happens whenever the machine next boots, not at a fixed time, so do not use it for anything that must happen at exactly 03:00.

Dependencies are easier to express. In the service you can add Wants=network-online.target and After=network-online.target so a backup to a remote host waits for the network. Cron has no equivalent, so people end up with a sleep 60 in the script.

Environment is a trap either way. User units do not read your interactive shell config, and they only run while you have a session unless you run loginctl enable-linger $USER. That one catches almost everyone once.

Cron is still simpler. One line, understood by anyone who has touched a server, no reload step, and it works on every box you will log into. If a job is "run this hourly, and I will notice if the output stops," cron is fine. Systemd is two files and a calendar syntax you have to learn. For the backup, I think that cost is worth paying.

SEE ALSO