How to Replace a Cron Job with a systemd Timer
A complete, working example converting a nightly backup cron job into a properly supervised systemd timer, with logging and failure visibility cron never gave you.
This converts a typical cron job — a nightly backup script — into an equivalent systemd timer, gaining proper logging, dependency ordering, and failure visibility that a bare cron entry doesn’t provide.
Step 1: the cron job you’re replacing
Assume you currently have this in root’s crontab:
0 2 * * * /usr/local/bin/backup.sh
Step 2: create the service unit
A systemd timer always pairs with a service unit describing what to actually run:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup job
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Type=oneshot tells systemd this service runs to completion and exits, rather than staying resident — the right type for a script that does its job and finishes, as opposed to a long-running daemon.
Step 3: create the timer unit
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup.service nightly at 2am
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true is the single biggest practical improvement over cron here — if the machine was powered off or suspended at 2am, the timer fires as soon as the system is next running, rather than silently skipping that day’s backup entirely the way cron would.
Step 4: enable and start the timer
systemctl daemon-reload
systemctl enable --now backup.timer
Note you enable and start the timer, not the service directly — the service only runs when the timer fires (or when triggered manually).
Step 5: verify the timer is scheduled correctly
systemctl list-timers backup.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-01-13 02:00:00 UTC 6h left n/a n/a backup.timer backup.service
Step 6: test it manually before waiting for 2am
systemctl start backup.service
systemctl status backup.service
Starting the service directly runs it immediately, independent of the timer’s schedule — the right way to test your script’s systemd integration without waiting for the actual scheduled time.
Step 7: check logs — the actual upgrade over cron
journalctl -u backup.service --since today
Every run’s output — stdout, stderr, exit status, and timing — is captured automatically in the systemd journal, queryable by time range, without needing to configure MAILTO or redirect output to a log file manually the way a cron job typically requires.
Step 8: get alerted on failure, not just logged
# add to backup.service's [Unit] section
OnFailure=notify-failure@%n.service
Pairing a failure with a notification unit (a small separate service that sends an alert, however you prefer to be notified) turns a silent cron failure into an actual, actionable alert — something cron has no native mechanism for at all.
Why this is worth the extra setup over a one-line crontab entry
A cron job that silently fails produces no signal at all unless you happen to notice a backup is missing — its output only goes anywhere if you’ve separately configured mail delivery, and a missed run (system was off) is simply skipped forever with no record. The systemd timer equivalent gives you queryable structured logs, Persistent=true catch-up semantics, and a natural hook for failure notifications, all using infrastructure already running on any modern systemd-based Linux system.