Cron has scheduled jobs on Unix for decades. On systemd systems, timers do the same job with extra benefits: logs in the journal, dependency handling, catching up on runs missed while the server was off, random delays to avoid thundering herds, and resource limits. Your distribution already uses them for apt-daily, logrotate and fstrim.

See existing timers

systemctl list-timers --all
NEXT                        LEFT     LAST                        PASSED   UNIT                 ACTIVATES
Thu 2026-10-15 00:00:00 UTC 9h left  Wed 2026-10-14 00:00:02 UTC 14h ago  logrotate.timer      logrotate.service
Thu 2026-10-15 06:12:31 UTC 15h left Wed 2026-10-14 06:40:11 UTC 8h ago   apt-daily-upgrade.timer apt-daily-upgrade.service

A timer needs two units

  1. A service that defines what runs.
  2. A timer that defines when.

Example: a nightly database backup.

# /etc/systemd/system/db-backup.service
[Unit]
Description=Nightly MySQL backup

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/db-backup.sh
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/db-backup.timer
[Unit]
Description=Run db-backup nightly

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

[Install]
WantedBy=timers.target

Enable the timer, not the service:

sudo systemctl daemon-reload
sudo systemctl enable --now db-backup.timer
systemctl list-timers db-backup.timer

Run the job right now for testing:

sudo systemctl start db-backup.service
journalctl -u db-backup.service -n 50

OnCalendar syntax

The format is DayOfWeek Year-Month-Day Hour:Minute:Second, where * means “any”:

Schedule OnCalendar
Every day at 03:15 *-*-* 03:15:00 or 03:15
Every hour hourly (*-*-* *:00:00)
Every 15 minutes *:0/15
Weekdays at 08:00 Mon..Fri 08:00
Sundays at 02:00 Sun 02:00
First day of the month *-*-01 00:00 or monthly
Twice a day *-*-* 06,18:00
In a time zone *-*-* 03:00 Europe/Berlin

Check an expression before using it:

systemd-analyze calendar '*:0/15'
systemd-analyze calendar --iterations=3 'Mon..Fri 08:00'

Relative timers (monotonic)

Instead of wall-clock times, run relative to events:

[Timer]
OnBootSec=5min            # 5 minutes after boot
OnUnitActiveSec=1h        # then every hour after the last run

This is useful for “every N minutes” jobs where the exact clock time doesn’t matter.

Useful options

  • Persistent=true — if the server was off at the scheduled time, run the job at the next boot. Cron would simply skip it.
  • RandomizedDelaySec=10m — spread start times, so many servers don’t hit a backup target or API at the same second.
  • AccuracySec=1s — by default, timers may fire up to 1 minute late to save wake-ups. Tighten it if precision matters.
  • Type=oneshot in the service — systemd waits for the script to finish, and a timer never starts a second copy while one is still running. Overlap protection is built in, unlike cron.

Logs and failures

Everything the job prints goes to the journal:

journalctl -u db-backup.service --since today
systemctl status db-backup.service       # last result, exit code
systemctl list-units --failed

Get notified on failure with OnFailure= in the service:

[Unit]
OnFailure=notify-failure@%n.service

[email protected] is a small template unit you write that sends an email or webhook.

Resource limits and security

Because the job is a regular service, all systemd settings apply:

[Service]
MemoryMax=512M
CPUQuota=50%
ProtectSystem=strict
ReadWritePaths=/var/backups
PrivateTmp=true

See systemd unit files.

Timers for a user (no root)

Put the units in ~/.config/systemd/user/ and use systemctl --user enable --now name.timer. To keep them running when you’re logged out, run sudo loginctl enable-linger $USER.

Timers vs cron

cron systemd timers
Setup One line Two small files
Logs Mail or your own redirect journald, automatically
Missed runs Skipped Persistent=true catches up
Overlap protection Manual (flock) Built in for oneshot services
Random delay Manual RandomizedDelaySec
Resource limits, sandboxing No Yes
Portability Everywhere systemd systems only

Cron is still perfectly fine for quick, simple jobs. See crontab. Use timers for important jobs where logging, catch-up and limits matter.

Quick reference

Task Command
List timers systemctl list-timers --all
Enable systemctl enable --now name.timer
Run job now systemctl start name.service
Logs journalctl -u name.service
Test schedule systemd-analyze calendar 'expr'
Disable systemctl disable --now name.timer

Related: cron expressions and database backup automation.

  • Linux
  • systemd
  • Automation