Skip to content
daniel@cosenza:~/blog
LinuxHow-To October 10, 2025 3 min read

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.