systemd Timers: Replace Cron Jobs with a Better Alternative

systemd Timers: Replace Cron Jobs with a Better Alternative

✅ Tested on Ubuntu 26.04, Debian 12, and Arch Linux · Last updated: June 2026

systemd timers are the modern replacement for cron jobs. A systemd timer is a unit file that triggers another systemd service on a schedule or after an event. Compared to cron, timers have logging (via journald), dependency management, the ability to catch up missed runs, and proper systemd integration for everything you already manage with systemctl. This guide covers creating timers, all scheduling options, converting cron jobs to timers, monotonic vs realtime triggers, and managing timers with systemctl.

Practicing this on a live server, or need one to follow along? See our comparison of the best Linux VPS hosting providers for 2026 if you need a Linux box to work on.

Contents
  1. What Are systemd Timers
  2. List Active Timers
  3. Create Your First Timer
  4. Calendar Time Syntax
  5. Monotonic Timers
  6. Realtime (Calendar) Timers
  7. Timer Options Reference
  8. Manage Timers with systemctl
  9. Logging and Debugging
  10. Converting Cron Jobs to Timers
  11. Practical Examples
  12. Timers vs Cron Comparison
  13. Frequently Asked Questions
    1. When should I use a timer vs cron?
    2. How do I run a timer as a specific user?
    3. Further Reading

What Are systemd Timers

Every systemd timer works in a pair: a .timer unit that defines the schedule, and a .service unit that defines the command to run. The timer activates the service.

# A timer pair lives in /etc/systemd/system/:
# mybackup.timer  ← defines WHEN to run
# mybackup.service ← defines WHAT to run

# Timers can be:
# - System-wide: /etc/systemd/system/*.timer (run as root or specified user)
# - User-specific: ~/.config/systemd/user/*.timer (run as your user)

# Unlike cron, timers:
# - Log to journald automatically
# - Handle missed runs (if system was off)
# - Have dependencies (wait for network, mount, etc.)
# - Support randomized delay to spread load
# - Are managed with familiar systemctl commands

List Active Timers

# List all active timers:
systemctl list-timers
# NEXT                        LEFT          LAST                        PASSED       UNIT
# Mon 2026-06-08 00:00:00 UTC 3h 45min left Sun 2026-06-07 00:00:00 UTC 20h ago      logrotate.timer
# Mon 2026-06-08 02:00:00 UTC 5h 45min left Sun 2026-06-07 02:00:10 UTC 18h ago      apt-daily.timer

# List ALL timers (including inactive):
systemctl list-timers --all

# Show only user timers:
systemctl --user list-timers

Create Your First Timer

Here's a complete example: a daily backup script that runs at 2:00 AM every night.

# Step 1: Create the script to run:
sudo nano /usr/local/bin/daily-backup.sh
#!/bin/bash
rsync -av /home/ /backup/home/
# (Make it executable)
sudo chmod +x /usr/local/bin/daily-backup.sh

# Step 2: Create the service unit:
sudo nano /etc/systemd/system/daily-backup.service
[Unit]
Description=Daily Home Directory Backup
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/daily-backup.sh
User=root

# Optional: send email on failure
# OnFailure=notify-email@%n.service
# Step 3: Create the timer unit:
sudo nano /etc/systemd/system/daily-backup.timer
[Unit]
Description=Run daily backup at 02:00

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
# Step 4: Enable and start the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now daily-backup.timer

# Verify it's scheduled:
systemctl status daily-backup.timer
systemctl list-timers daily-backup.timer

Calendar Time Syntax

The OnCalendar= directive uses a special syntax. The full format is: DayOfWeek Year-Month-Day Hour:Minute:Second. Asterisks are wildcards, commas separate values, and ranges use ...

# Test calendar expressions before using:
systemd-analyze calendar "Mon *-*-* 09:00:00"
# Output shows:
# Next elapse: Mon 2026-06-10 09:00:00 UTC
# From now:    1 day 22h left

systemd-analyze calendar "daily"
systemd-analyze calendar "weekly"
systemd-analyze calendar "0/2:00:00"    # every 2 hours
ExpressionMeaning
hourlyEvery hour (at :00:00)
dailyEvery day at 00:00:00
weeklyEvery Monday at 00:00:00
monthlyFirst of every month at 00:00:00
annuallyJanuary 1st at 00:00:00
*-*-* 02:30:00Every day at 2:30 AM
Mon *-*-* 09:00:00Every Monday at 9:00 AM
*-*-1 00:00:00First day of every month
*-*-1,15 00:00:001st and 15th of every month
Mon-Fri *-*-* 08:30:00Weekdays at 8:30 AM
*:0/15Every 15 minutes
0/6:00:00Every 6 hours (00:00, 06:00, 12:00, 18:00)
*-*-* *:00:00Every hour on the hour (same as hourly)
2026-*-* 12:00:00Every day during 2026 at noon

Monotonic Timers

Monotonic timers trigger relative to an event (system boot, service start) rather than a specific clock time. They're ideal for "run X minutes after boot" or "run every Y hours while system is up."

# Monotonic timer directives:

[Timer]
# Run 10 minutes after the system boots:
OnBootSec=10min

# Run 5 minutes after the timer unit itself is started:
OnActiveSec=5min

# Run 1 hour after the last time this service completed:
OnUnitActiveSec=1h

# Run 30 minutes after the last time this service was started:
OnUnitInactiveSec=30min

# Multiple triggers (run 5min after boot AND every 4 hours after that):
OnBootSec=5min
OnUnitActiveSec=4h

# Time units: us, ms, s (seconds), min, h, d, w, month, year
# Examples:
# 30s       = 30 seconds
# 5min 30s  = 5 minutes and 30 seconds
# 1h 30min  = 1.5 hours

Realtime (Calendar) Timers

# Key options for realtime timers:

[Timer]
# The schedule (uses calendar syntax):
OnCalendar=*-*-* 03:00:00

# Persistent: if the timer was missed (system off), run immediately on next boot:
Persistent=true

# Randomize start by up to 1 hour to spread load across multiple servers:
RandomizedDelaySec=1h

# Accuracy: how precisely to honor the time (default: 1min)
# Decreasing this wakes CPU more often, use for time-sensitive tasks:
AccuracySec=1s    # wake up exactly at scheduled time
AccuracySec=1h    # anywhere within 1 hour (default is 1min)

Timer Options Reference

OptionDescription
OnCalendar=Calendar-based schedule (realtime clock)
OnBootSec=Seconds after system boot
OnActiveSec=Seconds after timer activation
OnUnitActiveSec=Seconds after service last ran
Persistent=trueRun immediately if last trigger was missed
RandomizedDelaySec=Random delay up to this value (spread load)
AccuracySec=Timer precision (default: 1min)
Unit=Service to activate (default: same name as timer)
WakeSystem=trueWake system from sleep to run timer

Manage Timers with systemctl

# Enable timer (start on boot):
sudo systemctl enable daily-backup.timer

# Start timer now (without enabling):
sudo systemctl start daily-backup.timer

# Enable and start in one command:
sudo systemctl enable --now daily-backup.timer

# Stop the timer (no more scheduled runs):
sudo systemctl stop daily-backup.timer

# Disable (won't start on boot):
sudo systemctl disable daily-backup.timer

# Run the service right now (bypasses timer):
sudo systemctl start daily-backup.service

# Check timer status and next run time:
systemctl status daily-backup.timer

# Reload after editing unit files:
sudo systemctl daemon-reload

# User timers (run without sudo):
systemctl --user enable --now my-timer.timer
systemctl --user list-timers

Logging and Debugging

# View logs for the service (not the timer):
journalctl -u daily-backup.service

# Follow logs in real time:
journalctl -u daily-backup.service -f

# Logs from last run only:
journalctl -u daily-backup.service -n 50

# Logs since yesterday:
journalctl -u daily-backup.service --since yesterday

# Check if last run succeeded (exit code):
systemctl show daily-backup.service --property=ExecMainStatus
# ExecMainStatus=0 means success

# See when timer last triggered and when it triggers next:
systemctl show daily-backup.timer --property=LastTriggerUSec
systemctl show daily-backup.timer --property=NextElapseUSecRealtime

# Detailed timer status:
systemctl status daily-backup.timer
# ● daily-backup.timer - Run daily backup at 02:00
#      Loaded: loaded (/etc/systemd/system/daily-backup.timer; enabled)
#      Active: active (waiting) since Mon 2026-06-08
#     Trigger: Tue 2026-06-09 02:00:00 UTC; 21h left
# Trig. by: daily-backup.service
#    Triggers: ● daily-backup.service

Converting Cron Jobs to Timers

Here's a translation guide for common cron expressions:

Cron expressionsystemd OnCalendar=
@hourlyhourly
@dailydaily
@weeklyweekly
@monthlymonthly
@rebootUse OnBootSec=
0 2 * * **-*-* 02:00:00
30 8 * * 1Mon *-*-* 08:30:00
0 0 1 * **-*-1 00:00:00
*/15 * * * **:0/15
0 */6 * * *0/6:00:00
0 8 * * 1-5Mon-Fri *-*-* 08:00:00
# Converting: cron job that runs a script as www-data at 3 AM daily:
# Cron: 0 3 * * * www-data /usr/local/bin/cleanup.sh

# Service file:
# /etc/systemd/system/cleanup.service
[Unit]
Description=Daily Cleanup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup.sh
User=www-data

# Timer file:
# /etc/systemd/system/cleanup.timer
[Unit]
Description=Run daily cleanup at 03:00

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

[Install]
WantedBy=timers.target

Practical Examples

# Example 1: Run a Python script every 15 minutes
# /etc/systemd/system/data-collector.service
[Unit]
Description=Data Collection Script

[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /opt/collector/run.py
User=collector
WorkingDirectory=/opt/collector

# /etc/systemd/system/data-collector.timer
[Unit]
Description=Run data collector every 15 minutes

[Timer]
OnCalendar=*:0/15
RandomizedDelaySec=60    # random 0-60s delay so not all servers hit API at once

[Install]
WantedBy=timers.target
# Example 2: Disk usage report sent to email (Monday mornings)
# /etc/systemd/system/disk-report.service
[Unit]
Description=Weekly Disk Usage Report

[Service]
Type=oneshot
ExecStart=/bin/bash -c "df -h | mail -s 'Weekly Disk Report' admin@example.com"
User=root

# /etc/systemd/system/disk-report.timer
[Unit]
Description=Weekly disk report on Monday

[Timer]
OnCalendar=Mon *-*-* 07:00:00
Persistent=false    # if missed, skip it (not critical)

[Install]
WantedBy=timers.target
# Example 3: User-level timer (no sudo needed)
# ~/.config/systemd/user/sync-notes.service
[Unit]
Description=Sync Notes to Cloud

[Service]
Type=oneshot
ExecStart=/home/user/bin/sync-notes.sh

# ~/.config/systemd/user/sync-notes.timer
[Unit]
Description=Sync notes every 30 minutes

[Timer]
OnUnitActiveSec=30min
OnBootSec=5min

[Install]
WantedBy=default.target

# Enable user timer:
systemctl --user daemon-reload
systemctl --user enable --now sync-notes.timer

Timers vs Cron Comparison

Featuresystemd Timerscron
Loggingjournald, full logs, searchableEmail or manual redirect to file
Missed run handlingPersistent=true catches upMissed runs are skipped permanently
DependenciesAfter=network.target etc.Not supported
Run as different userUser= in service fileSpecify user in crontab line
Load spreadingRandomizedDelaySec=Manual offset in schedule
Monitoringsystemctl status, list-timersNo built-in status
On-demand runsystemctl start service.serviceRun script directly
ComplexityTwo files requiredOne line in crontab
User-level scheduling~/.config/systemd/user/crontab -e
Availabilitysystemd systems onlyAll Unix systems

Frequently Asked Questions

When should I use a timer vs cron?

Use timers when: you need logging, the task has system dependencies (network, mount), you care about missed runs being caught up, or you want random delay to spread server load. Use cron when: you need portability (non-systemd systems like macOS), you're writing a quick one-off job and don't want two files, or you're maintaining existing cron infrastructure. For new Linux server work, timers are generally better: the extra file is worth it for the logging alone.

How do I run a timer as a specific user?

Add User=username and optionally Group=groupname to the [Service] section. The service will run with that user's environment and permissions. For system-wide timers that need to write to user home directories, also check that the paths in ExecStart= are accessible to that user. Alternatively, use user-level timers in ~/.config/systemd/user/ if the user account is available.

systemd timers integrate well with cron knowledge: both schedule tasks, but timers use systemd's service infrastructure. For more systemd patterns, our process management guide covers managing services and processes. The logging guide covers reading journalctl output from timer runs in depth.


JM Carreira

JM Carreira

I’ve used Linux since 2008 and it has been my main system since 2021. When an article has a “Tested on” box, I ran the steps on my Lenovo Tiny. More about me and the test machine.

Go up

This site uses cookies for analytics and advertising (Google AdSense). By continuing to browse, you accept our use of cookies. Read our privacy policy