Monotonic Timers
By the end of this lesson you will understand all four monotonic timer directives, know exactly when each fires, combine them with calendar timers for hybrid scheduling, and choose the right directive for heartbeats, retries, cooldowns, and startup tasks.
What Are Monotonic Timers?
Monotonic timers count time relative to lifecycle events — not the wall clock. They use the system's monotonic clock, which counts elapsed seconds since boot and is unaffected by timezone changes or NTP adjustments.
The Four Monotonic Directives
| Directive | Counts From | Use Case |
|---|---|---|
OnBootSec= | System boot (kernel start) | One-time post-boot tasks |
OnStartupSec= | systemd manager start (PID 1 ready) | Service-manager-level hooks |
OnUnitActiveSec= | Last time the timer itself was activated | Fixed interval between runs |
OnUnitInactiveSec= | Last time the triggered service became inactive (finished) | Cooldown / retry after completion |
OnBootSec — Run After Boot
What It Does
Fires once, a specified duration after the system boots. Does not repeat.
When To Use
- Post-boot initialization that needs a stable system before running.
- Warm-up tasks (cache priming, health registration).
- One-shot setup that should not run at every restart of PID 1.
Example
[Unit]
Description=Run setup 2 minutes after boot
[Timer]
OnBootSec=2min
[Install]
WantedBy=timers.target
[Unit]
Description=Post-boot initialization
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/post-boot-setup.sh
RemainAfterExit=yes
Behavior Details
| Question | Answer |
|---|---|
| Fires on first boot after enabling? | Yes, 2 minutes after boot |
| Fires again without reboot? | No — only counts from boot |
Works with Persistent=? | No — Persistent= only affects OnCalendar= |
OnStartupSec — Run After systemd Manager Start
What It Does
Similar to OnBootSec=, but counts from when the systemd manager (PID 1) finishes its startup. For system units, this is nearly identical to OnBootSec=. For user units, it counts from when the user's systemd instance starts (login).
When To Use
- User-level timers that should fire after the user logs in.
- System-level hooks that need the full systemd manager initialized.
Example
[Unit]
Description=Run warmup 30 seconds after user session starts
[Timer]
OnStartupSec=30s
[Install]
WantedBy=default.target
OnBootSec vs OnStartupSec
| Directive | System units | User units |
|---|---|---|
OnBootSec= | Counts from kernel boot | Not commonly used |
OnStartupSec= | Counts from PID 1 ready (nearly same as boot) | Counts from user session start |
For system-level timers, the difference is negligible. For user-level timers, use OnStartupSec=.
OnUnitActiveSec — Fixed Interval Between Runs
What It Does
Fires a specified duration after the timer was last activated (started). This creates a fixed interval between runs, regardless of how long the service takes to execute.
When To Use
- Heartbeats and health checks — run every 60 seconds.
- Polling jobs — check a queue or API endpoint at regular intervals.
- Metrics collection — gather system stats every N minutes.
Example
[Unit]
Description=Heartbeat every 60 seconds
[Timer]
OnUnitActiveSec=60s
[Install]
WantedBy=timers.target
Timing Behavior
Timer activated at T=0
→ Service runs at T=0 (takes 5 seconds)
→ Timer fires again at T=60 (not T=65)
→ Service runs at T=60 (takes 5 seconds)
→ Timer fires again at T=120
The interval is measured from activation to activation, not from completion to next activation. If your service takes 5 seconds, the interval between the start of consecutive runs is still 60 seconds.
If your service takes longer than the interval (e.g., 90 seconds for a 60-second interval), systemd waits for the service to finish before triggering the next run. The next run fires immediately after completion, but intervals are not "stacked."
OnUnitInactiveSec — Cooldown After Completion
What It Does
Fires a specified duration after the triggered service becomes inactive (finishes execution). This creates a cooldown — the timer waits for the service to finish, then counts from that completion time.
When To Use
- Retry loops — wait 20 minutes after a job finishes before trying again.
- Variable-duration jobs — ensure a gap between runs regardless of execution time.
- Throttled processing — process a queue item, wait, then process the next.
Example
[Unit]
Description=Retry worker 20 minutes after each run completes
[Timer]
OnUnitInactiveSec=20m
[Install]
WantedBy=timers.target
Timing Behavior
Service starts at T=0
→ Service finishes at T=45s (variable duration)
→ Timer counts 20 minutes from T=45s
→ Service starts again at T=20m45s
→ Service finishes at T=21m30s
→ Timer counts 20 minutes from T=21m30s
→ Service starts again at T=41m30s
OnUnitActiveSec vs OnUnitInactiveSec
| Aspect | OnUnitActiveSec=10m | OnUnitInactiveSec=10m |
|---|---|---|
| Measures from | Timer activation | Service completion |
| With 5min runtime | Next run at T=10, T=20, T=30 | Next run at T=15, T=30, T=45 |
| With variable runtime | Interval stays constant | Gap between runs stays constant |
| Best for | Regular interval (heartbeats) | Controlled gap (retries, throttling) |
Combining Monotonic Directives
You can combine multiple monotonic directives in one timer. The service fires when any of them would trigger:
Boot + Recurring
[Timer]
OnBootSec=30s # Run 30 seconds after boot
OnUnitActiveSec=1h # Then repeat every hour
Behavior:
- System boots → waits 30 seconds → service runs.
- After each run, waits 1 hour → service runs again.
- Repeats forever.
Boot + Cooldown
[Timer]
OnBootSec=1min # Run 1 minute after boot
OnUnitInactiveSec=15m # Then 15 minutes after each completion
Combining Calendar + Monotonic
You can also combine OnCalendar= with monotonic directives:
[Timer]
OnCalendar=02:15 # Run at 2:15 AM daily
OnBootSec=5min # Also run 5 minutes after every boot
Persistent=true # Catch up missed calendar runs
Behavior:
- Fires at 2:15 AM every day (calendar).
- Also fires 5 minutes after every boot (monotonic).
- If the system was down during 2:15 AM,
Persistent=truecatches up on next boot.
When combining multiple directives, the logic is OR — the timer fires when any individual directive's condition is met. The service only runs once per trigger (no double-firing if both conditions match simultaneously).
Duration Format
All monotonic directives accept systemd's duration format:
| Suffix | Meaning | Example |
|---|---|---|
us / usec | Microseconds | 500us |
ms / msec | Milliseconds | 100ms |
s / sec / seconds | Seconds | 30s |
m / min / minutes | Minutes | 5m |
h / hr / hours | Hours | 2h |
d / days | Days | 1d |
w / weeks | Weeks | 1w |
M / months | Months (30.44 days) | 1M |
y / years | Years (365.25 days) | 1y |
Compound values:
OnBootSec=1h30m # 1 hour and 30 minutes after boot
OnUnitActiveSec=2m30s # Every 2 minutes and 30 seconds
The Initial Trigger Problem
A common confusion with OnUnitActiveSec=:
Problem: You create a timer with OnUnitActiveSec=60s and enable it, but the service never fires. Why?
Reason: OnUnitActiveSec= counts from the last activation. If the timer was just enabled and has never been activated, there's no reference point. systemd fires it immediately on the first activation, then starts counting from there.
However, if you restart the timer (systemctl restart), the countdown resets. To guarantee an initial run, add OnBootSec=0:
[Timer]
OnBootSec=0 # Fire immediately after boot
OnUnitActiveSec=60s # Then every 60 seconds
Or start the service manually once:
sudo systemctl start myservice.service # Initial run
sudo systemctl enable --now mytimer.timer # Then automatic
Practical Patterns
Pattern 1: Heartbeat (Every 60 Seconds)
[Timer]
OnBootSec=10s # First run 10 seconds after boot
OnUnitActiveSec=60s # Then every 60 seconds
Pattern 2: Retry with Backoff (20 Minutes After Completion)
[Timer]
OnUnitInactiveSec=20m # Wait 20 minutes after job finishes
Pattern 3: Boot Task Only (One-Shot)
[Timer]
OnBootSec=2min # Run once, 2 minutes after boot
Pattern 4: Hybrid Calendar + Boot Catch-Up
[Timer]
OnCalendar=02:15 # Daily at 2:15 AM
OnBootSec=5min # Also 5 minutes after boot (catch up)
Persistent=true # Plus catch-up for calendar misses
Pattern 5: User Session Periodic Task
[Timer]
OnStartupSec=10s # 10 seconds after login
OnUnitActiveSec=5m # Then every 5 minutes
Key Takeaways
OnBootSec=fires once after boot — for initialization tasks.OnUnitActiveSec=fires at fixed intervals — for heartbeats and polling.OnUnitInactiveSec=fires after completion — for cooldowns and retries.OnStartupSec=is likeOnBootSec=but counts from the systemd manager start — useful for user units.- Combine directives freely — they're OR-ed, and you can mix calendar + monotonic.
- Duration supports compound values:
1h30m,2m30s. - Add
OnBootSec=0alongsideOnUnitActiveSec=to guarantee an initial trigger.
What's Next
- Unit File Anatomy — complete reference for every directive in the
.timerand.serviceunit files.