LFCS 64 🐧 systemd Timers
systemd timers schedule work without cron. The timer unit defines when a job runs, and a paired service unit defines what runs. The two files share a base name: backup.timer triggers backup.service. The exam tests this pairing, the OnCalendar= syntax that replaces cron expressions, the Persistent=true directive that catches missed runs after downtime, and the critical enablement detail: systemctl enable --now targets the .timer, not the .service .
The timer model has two scheduling families. Real-time timers use OnCalendar= with calendar expressions that resemble cron but are more expressive: Mon..Fri *-*-* 10:00:00 runs weekdays at 10 AM, and *-*-* *:00/15:00 runs every 15 minutes . Monotonic timers use OnBootSec=, OnUnitActiveSec=, and OnActiveSec= to schedule relative to boot or the last activation, independent of wall-clock time . A single timer can combine both: run five minutes after boot and also every weekday at 10 AM.
This chapter covers the timer-service pairing, OnCalendar= and monotonic directives, Persistent= for missed runs, the systemctl list-timers inspection command, and the exam traps that turn a scheduled job into one that never fires. It assumes familiarity with custom service units from the preceding chapters.
Key point: A timer is a trigger, not a task. The .timer file must reference a .service file with the same base name (or an explicit Unit= directive), and the exam expects both files to exist. Enabling the service instead of the timer produces a job that never runs on schedule.
Why systemd timers are an exam domain
The timer-service pairing is the first trap. A candidate who writes only a .service file and enables it has created a service that runs once at boot and never again. The LFCS practice guidance is explicit: the standard answer to “run a script daily at 3 AM” is two files—backup.service and backup.timer—followed by systemctl enable --now backup.timer . The .service contains the ExecStart= command; the .timer contains the schedule.
OnCalendar= syntax is tested, not just mentioned. The exam may ask for a specific schedule: weekdays only, every 15 minutes, or a particular day of the month. The syntax is documented in man systemd.time, and systemd-analyze calendar validates expressions before deployment . A malformed OnCalendar= line can be silently ignored if the timer has another scheduling directive, making verification essential.
Persistent=true distinguishes a scheduled job from a reliable one. Without it, a timer that was due to fire while the system was powered off does not run when the system comes back. With it, systemd records the last trigger time and fires the missed run immediately after boot . For backups and maintenance tasks, this is the difference between a job that runs and one that silently skips.
The trade-off. Timers are more verbose than a crontab line, but they provide dependency ordering (After=), resource isolation (User=), journal integration, and the ability to catch missed runs. The exam tests the verbosity because production schedulers need it.
a. The timer-service pair and basic creation
A timer file lives in /etc/systemd/system/ and has the .timer extension. Its [Timer] section contains the scheduling directives and, optionally, a Unit= directive pointing to the service to trigger. If Unit= is omitted, systemd looks for a service with the same base name: backup.timer triggers backup.service .
# /etc/systemd/system/backup.service
[Unit]
Description=Backup job
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
The service uses Type=oneshot because the script runs and exits. The timer’s [Install] section uses WantedBy=timers.target, not multi-user.target. The timers.target is the target that systemd uses to activate all timers at boot .
The enablement command targets the timer: systemctl enable --now backup.timer. Starting backup.service manually runs the job once; starting backup.timer activates the schedule. The systemctl list-timers command shows the next and last trigger times, which is the verification step .
b. OnCalendar= and monotonic scheduling
OnCalendar= accepts a calendar expression. The format is DayOfWeek Year-Month-Day Hour:Minute:Second, with ranges, lists, and step values. Examples from the SUSE documentation and common practice include:
| Schedule | Expression |
|---|---|
| Daily at 3 AM | *-*-* 03:00:00 |
| Weekdays at 10 AM | Mon..Fri *-*-* 10:00:00 |
| Every 15 minutes | *-*-* *:00/15:00 |
| First of month | *-*-01 00:00:00 |
| Mondays at 4 AM | Mon *-*-* 04:00:00 |
The .. range and , list syntax are supported: Mon,Tue *-*-01..04 12:00:00 means the first four days of the month at noon, if those days are Monday or Tuesday . The systemd-analyze calendar command validates and explains expressions before deployment .
Monotonic directives schedule relative to events rather than wall-clock time. OnBootSec=5min fires five minutes after boot. OnUnitActiveSec=24h fires 24 hours after the service was last activated . These are useful for jobs that should run at a fixed interval after the previous run, not at a fixed time of day.
A timer can combine real-time and monotonic directives. The SUSE example uses OnBootSec=5min, OnUnitActiveSec=24h, and OnCalendar=Mon..Fri *-*-* 10:00 in the same [Timer] section . Each directive adds a trigger; the service runs when any condition is met.
c. Persistent=, RandomizedDelaySec=, and verification
Persistent=true is the directive that handles missed runs. systemd stores the last trigger time on disk. When the timer activates, if a scheduled run was missed while the system was off, systemd triggers the service immediately . The SUSE documentation states this explicitly: “If the system is off during the expected execution time, the timer is executed once the system is running again” .
RandomizedDelaySec= adds a random delay before the trigger. This prevents multiple timers from firing at the same instant and overwhelming the system. A value like 2min means the service starts at a random time within two minutes of the scheduled time . For jobs that need precise timing, set it to 0 or omit it.
Verification has three commands. systemctl list-timers --all shows all timers with next and last elapse times . systemctl status backup.timer shows the timer’s active state and the service it triggers. journalctl -u backup.service shows the output of the service’s runs, which is where a failed script’s error appears .
A subtle trap: malformed directives in a timer with another valid scheduling directive can be silently ignored. The SUSE documentation describes a timer with OnClendar= (misspelled) and a valid OnBootSec=. The misspelled line is ignored, and the weekday trigger never fires. The only way to detect this is systemd-analyze verify FILE .
Complete Example Session
# ============================================
# PART 1: THE SERVICE (WHAT RUNS)
# ============================================
# File: /etc/systemd/system/backup.service
[Unit]
Description=Daily backup job
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backupuser
# ============================================
# PART 2: THE TIMER (WHEN IT RUNS)
# ============================================
# File: /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 3 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=5min
[Install]
WantedBy=timers.target
# ============================================
# PART 3: SECTION BREAKDOWN
# ============================================
# [Timer]
# OnCalendar=*-*-* 03:00:00 → daily at 3 AM.
# Persistent=true → catch missed runs after downtime.
# RandomizedDelaySec=5min → spread load.
#
# [Install]
# WantedBy=timers.target → enable at boot.
# NOT multi-user.target. Timers use timers.target.
# ============================================
# PART 4: VERIFY BEFORE ENABLING
# ============================================
# Check syntax.
systemd-analyze verify /etc/systemd/system/backup.timer
# Validate the calendar expression.
systemd-analyze calendar "*-*-* 03:00:00"
# ============================================
# PART 5: RELOAD, ENABLE, VERIFY
# ============================================
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
# Verify the timer is active.
systemctl is-active backup.timer # active
systemctl list-timers backup.timer # shows next trigger
# ============================================
# PART 6: MONOTONIC SCHEDULING
# ============================================
# Run 5 minutes after boot, then every 24h.
# [Timer]
# OnBootSec=5min
# OnUnitActiveSec=24h
#
# These are independent of wall-clock time.
# ============================================
# PART 7: COMBINED REAL-TIME AND MONOTONIC
# ============================================
# [Timer]
# OnBootSec=5min
# OnUnitActiveSec=24h
# OnCalendar=Mon..Fri *-*-* 10:00
#
# Service runs when ANY condition is met.
# ============================================
# PART 8: THE SILENT IGNORE TRAP
# ============================================
# A misspelled OnClendar= with a valid OnBootSec=
# is silently ignored. The weekday trigger never fires.
# Only systemd-analyze verify catches it.
systemd-analyze verify /etc/systemd/system/backup.timer
# Output: Unknown key name 'OnClendar' in section 'Timer'
# ============================================
# PART 9: INSPECT AND TROUBLESHOOT
# ============================================
# List all timers.
systemctl list-timers --all
# Status of the timer.
systemctl status backup.timer
# Service output (where the script's errors appear).
journalctl -u backup.service -n 50
# ============================================
# PART 10: THE EXAM CHECKLIST
# ============================================
# 1. Create backup.service (Type=oneshot, ExecStart).
# 2. Create backup.timer (OnCalendar, Persistent).
# 3. [Install] WantedBy=timers.target.
# 4. systemd-analyze verify backup.timer.
# 5. systemctl daemon-reload.
# 6. systemctl enable --now backup.timer.
# 7. Verify: systemctl list-timers backup.timer.
# The service is NOT enabled; the timer is.
Quick Reference
Timer vs Service
| File | Purpose | Enable Target |
|---|---|---|
backup.service | Defines the command (ExecStart) | Not enabled directly |
backup.timer | Defines the schedule | enable --now backup.timer |
Scheduling Directives
| Directive | Type | Example |
|---|---|---|
OnCalendar= | Real-time | *-*-* 03:00:00 |
OnBootSec= | Monotonic | 5min |
OnUnitActiveSec= | Monotonic | 24h |
OnActiveSec= | Monotonic | 1h |
Common OnCalendar= Patterns
| Need | Expression |
|---|---|
| Daily 3 AM | *-*-* 03:00:00 |
| Weekdays 10 AM | Mon..Fri *-*-* 10:00:00 |
| Every 15 min | *-*-* *:00/15:00 |
| Monthly 1st | *-*-01 00:00:00 |
Verification Commands
| Command | Purpose |
|---|---|
systemd-analyze verify <timer> | Check syntax |
systemd-analyze calendar "<expr>" | Validate schedule |
systemctl list-timers --all | Next/last trigger times |
journalctl -u <service> | Service output |
Best Practices
✅ Do This:
# Use timers.target in [Install]. // ✅
[Install]
WantedBy=timers.target
# Set Persistent=true for backups/maintenance. // ✅
Persistent=true
# Use Type=oneshot for timer-triggered services. // ✅
[Service]
Type=oneshot
# Verify with systemd-analyze before enabling. // ✅
systemd-analyze verify backup.timer
❌ Don’t Do This:
# Enable the service instead of the timer. // ❌
systemctl enable backup.service # Never runs on schedule.
# Use multi-user.target for timer [Install]. // ❌
WantedBy=multi-user.target # Timer not activated by timers.target.
# Omit Persistent=true for critical jobs. // ❌
# Missed runs are silently skipped.
# Trust a timer without verification. // ❌
# Misspelled directives are silently ignored.
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Timer never fires | Enabled .service, not .timer | enable --now <name>.timer |
| Missed run after reboot | Persistent not set | Persistent=true |
| Weekday trigger ignored | Misspelled OnCalendar= with valid OnBootSec= | systemd-analyze verify |
| Timer not active at boot | [Install] uses multi-user.target | Use timers.target |
| Schedule not what expected | Calendar expression wrong | systemd-analyze calendar |
Real-World Examples
1. Daily Backup at 3 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
2. Every 15 Minutes
OnCalendar=*-*-* *:00/15:00
3. Weekdays at 10 AM
OnCalendar=Mon..Fri *-*-* 10:00:00
4. Run After Boot
OnBootSec=5min
5. Combined Trigger
OnBootSec=5min
OnUnitActiveSec=24h
6. Oneshot Service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/job.sh
7. Enable the Timer
sudo systemctl enable --now backup.timer
8. List Timers
systemctl list-timers --all
9. Verify Syntax
systemd-analyze verify backup.timer
10. Check Service Output
journalctl -u backup.service -n 50
Visual
Timer-Service Pair
┌──────────────────────────────────────────────┐
│ backup.timer backup.service │
│ (WHEN) (WHAT) │
│ │ │ │
│ OnCalendar= ExecStart= │
│ Persistent= Type=oneshot │
│ │ │ │
│ └──── triggers ────────┘ │
│ │
│ Same base name = automatic pairing. │
│ Unit= overrides if different name. │
│ │
│ enable --now backup.timer │
│ NOT backup.service. │
│ │
└──────────────────────────────────────────────┘
Real-Time vs Monotonic
┌──────────────────────────────────────────────┐
│ REAL-TIME (OnCalendar) │
│ Fires at wall-clock time. │
│ "Every day at 3 AM." │
│ Depends on system clock. │
│ │
│ MONOTONIC (OnBootSec, OnUnitActiveSec) │
│ Fires relative to an event. │
│ "5 minutes after boot." │
│ "24 hours after last run." │
│ │
│ Both can coexist in one [Timer]. │
│ Any condition triggers the service. │
│ │
└──────────────────────────────────────────────┘
Persistent Behavior
┌──────────────────────────────────────────────┐
│ Persistent=false (default) │
│ 3 AM scheduled. System off at 3 AM. │
│ System boots at 8 AM. │
│ → No run. Missed forever. │
│ │
│ Persistent=true │
│ 3 AM scheduled. System off at 3 AM. │
│ System boots at 8 AM. │
│ → Run immediately after boot. │
│ │
│ systemd stores last trigger timestamp. │
│ │
└──────────────────────────────────────────────┘
Verification Flow
┌──────────────────────────────────────────────┐
│ BEFORE ENABLING │
│ │
│ systemd-analyze verify backup.timer │
│ → Syntax OK? │
│ │
│ systemd-analyze calendar "*-*-* 03:00:00" │
│ → Next elapse: correct? │
│ │
│ AFTER ENABLING │
│ │
│ systemctl list-timers backup.timer │
│ → NEXT column populated? │
│ → ACTIVATES shows backup.service? │
│ │
│ journalctl -u backup.service │
│ → Service ran? Errors? │
│ │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Timer file | <name>.timer |
| Service file | <name>.service (same base name) |
| Enable target | enable --now <name>.timer |
[Install] target | WantedBy=timers.target |
| Real-time scheduling | OnCalendar= |
| Monotonic scheduling | OnBootSec=, OnUnitActiveSec= |
| Missed run handling | Persistent=true |
| Load spreading | RandomizedDelaySec= |
| List timers | systemctl list-timers --all |
| Syntax validation | systemd-analyze verify |
Key takeaways:
- Timers are triggers; services are tasks. The
.timerfile schedules, the.servicefile executes. They share a base name, and the exam expects both files. Enabling the service instead of the timer produces a job that never runs on schedule . OnCalendar=replaces cron with more expressive syntax.Mon..Fri *-*-* 10:00:00runs weekdays at 10 AM.systemd-analyze calendarvalidates expressions before deployment .Persistent=truecatches missed runs. Without it, a timer due while the system was off does not fire after boot. With it, the missed run triggers immediately .- The
[Install]target for timers istimers.target, notmulti-user.target. This is the target that activates all timers at boot. - Malformed directives can be silently ignored. A misspelled
OnClendar=with a validOnBootSec=produces no error at runtime; onlysystemd-analyze verifycatches it . systemctl list-timersis the verification command. It shows the next trigger, the last trigger, and the service each timer activates.
Remember: A systemd timer is a scheduled trigger for a service. The .timer file says when; the .service file says what. They pair by name, and the timer is what gets enabled. OnCalendar= handles wall-clock schedules, OnBootSec= and OnUnitActiveSec= handle relative schedules, and Persistent=true ensures a missed run is not silently skipped. The exam grader checks that the timer is enabled and that the service actually runs on schedule. systemd-analyze verify before enable --now catches the syntax errors that would otherwise produce a timer that looks active but never fires.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!