Skip to main content

Cheatsheet and Quiz

Learning Focus

Use this page as a quick reference after you've completed the full documentation. The cheat sheet is designed for copy-paste during daily operations. The quiz tests your understanding of key concepts.


Cheat Sheet

Complete systemd timer Cheat Sheet — Click to Expand
systemd-timer-cheat-sheet.txt
# ══════════════════════════════════════════════════════════════════════════════
# systemd timer — Quick Reference Cheat Sheet
# ══════════════════════════════════════════════════════════════════════════════


# ── STEP 1: Create the Timer Unit ─────────────────────────────────────────────
# File: /etc/systemd/system/myjob.timer

[Unit]
Description=Run my job on a schedule

[Timer]
OnCalendar=02:15 # Calendar-based (see patterns below)
Persistent=true # Catch up missed runs after downtime
RandomizedDelaySec=5m # Fleet jitter: 0–5 min random delay
FixedRandomDelay=true # Stable offset per unit across runs
AccuracySec=1m # Coalescing window (lower = more precise)
# Unit=another.service # Override default name match

[Install]
WantedBy=timers.target


# ── STEP 2: Create the Service Unit ──────────────────────────────────────────
# File: /etc/systemd/system/myjob.service

[Unit]
Description=My scheduled job
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=www-data
Group=www-data
WorkingDirectory=/var/www/html
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/usr/bin/flock -n /var/lock/myjob.lock /usr/local/bin/myjob.sh
RuntimeMaxSec=1h
StandardOutput=append:/var/log/myjob.log
StandardError=append:/var/log/myjob.log
NoNewPrivileges=true # Basic hardening
PrivateTmp=true
# No [Install] section — activated by the .timer unit


# ── STEP 3: Activate ─────────────────────────────────────────────────────────
sudo systemctl daemon-reload
sudo systemctl enable --now myjob.timer


# ── Management Commands ──────────────────────────────────────────────────────
sudo systemctl daemon-reload # Re-read unit files (REQUIRED)
sudo systemctl enable --now myjob.timer # Enable on boot + start now
sudo systemctl disable --now myjob.timer # Disable + stop
sudo systemctl start myjob.timer # Start the timer
sudo systemctl stop myjob.timer # Stop the timer
sudo systemctl restart myjob.timer # Restart the timer
systemctl status myjob.timer # Check schedule / next fire
systemctl status myjob.service # Check service result / exit code


# ── Inspect ──────────────────────────────────────────────────────────────────
systemctl list-timers --all --no-pager # All timers with NEXT/LAST/PASSED
systemctl list-timers myjob.timer # Specific timer
systemctl show myjob.timer -p NextElapseUSecRealtime -p LastTriggerUSec
systemctl cat myjob.timer # Show unit file content


# ── Logs ─────────────────────────────────────────────────────────────────────
journalctl -u myjob.service -f # Follow live output
journalctl -u myjob.service -n 50 # Last 50 lines
journalctl -u myjob.service --since today # Today's logs
journalctl -u myjob.service -p err # Errors only
journalctl -u myjob.timer -u myjob.service # Combined view


# ── Test ─────────────────────────────────────────────────────────────────────
sudo systemctl start myjob.service # Run service immediately (no wait)
journalctl -u myjob.service -n 20 # Check result


# ── Schedule Validation ──────────────────────────────────────────────────────
systemd-analyze calendar --iterations=5 'Mon..Fri 06:00'
systemd-analyze calendar --iterations=3 '*:0/5'
systemd-analyze calendar --iterations=3 '*-*~1 00:00:00'


# ── Verify + Security ────────────────────────────────────────────────────────
sudo systemd-analyze verify myjob.timer myjob.service
systemd-analyze security myjob.service


# ── Persistent State ─────────────────────────────────────────────────────────
sudo systemctl clean --what=state myjob.timer


# ── User Timers (no root) ────────────────────────────────────────────────────
# Files go in: ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now myjob.timer
systemctl --user list-timers --no-pager
sudo loginctl enable-linger "$USER" # Persist after logout


# ── Full Cleanup ─────────────────────────────────────────────────────────────
sudo systemctl disable --now myjob.timer
sudo rm /etc/systemd/system/myjob.timer /etc/systemd/system/myjob.service
sudo systemctl daemon-reload
sudo systemctl reset-failed

OnCalendar Quick Reference

PatternExpressionValidate
Every minuteminutelysystemd-analyze calendar minutely
Every 5 minutes*:0/5systemd-analyze calendar '*:0/5'
Every 15 minutes*:0/15systemd-analyze calendar '*:0/15'
Every 30 minutes*:0/30systemd-analyze calendar '*:0/30'
Every hourhourlysystemd-analyze calendar hourly
Every 2 hours*-*-* 0/2:00:00systemd-analyze calendar '*-*-* 0/2:00:00'
Every 6 hours*-*-* 00/6:00:00systemd-analyze calendar '*-*-* 00/6:00:00'
Midnight dailydailysystemd-analyze calendar daily
Daily at 2:15 AM02:15systemd-analyze calendar '02:15'
Twice daily09,21:00systemd-analyze calendar '09,21:00'
Peak hours (8-8, /10m)*-*-* 08..20:0/10systemd-analyze calendar '*-*-* 08..20:0/10'
Weekdays at 6 AMMon..Fri 06:00systemd-analyze calendar 'Mon..Fri 06:00'
Monday at 3 AMMon 03:00systemd-analyze calendar 'Mon 03:00'
Saturday at 2 AMSat 02:00systemd-analyze calendar 'Sat 02:00'
1st of monthmonthlysystemd-analyze calendar monthly
1st and 15th*-*-01,15 04:00systemd-analyze calendar '*-*-01,15 04:00'
Last day of month*-*~1 00:00:00systemd-analyze calendar '*-*~1 00:00:00'
First MondayMon *-*-1..7 03:00systemd-analyze calendar 'Mon *-*-1..7 03:00'
Quarterlyquarterlysystemd-analyze calendar quarterly
Yearlyyearlysystemd-analyze calendar yearly

Monotonic Timer Reference

DirectiveCounts FromExample
OnBootSec=System bootOnBootSec=2min
OnStartupSec=systemd manager startOnStartupSec=30s
OnUnitActiveSec=Timer activation (fixed interval)OnUnitActiveSec=1h
OnUnitInactiveSec=Service completion (cooldown)OnUnitInactiveSec=20m

Debugging Quick Reference

SymptomLikely CauseFix
Timer never firesBad calendar expression or daemon-reload not runValidate + reload
Unit file errorSyntax error in unit filesystemd-analyze verify
Service fails immediatelyWrong path, permissions, or envCheck User=, absolute paths, journal
Missed runs after rebootPersistent=true missingAdd it
Fleet thundering herdNo jitterAdd RandomizedDelaySec=
Job overlaps itselfNo flock or runtime limitAdd flock -n + RuntimeMaxSec=
Timer state stalePersistent state corruptedsystemctl clean --what=state
User timer stops on logoutLinger not enabledloginctl enable-linger

Hands-On Lab

Lab 1 — Basic Timer (5 minutes)

  1. Create /etc/systemd/system/lab-timer.service with Type=oneshot and ExecStart=/bin/sh -c 'echo "Timer fired at $(date)" >> /tmp/lab-timer.log'.
  2. Create /etc/systemd/system/lab-timer.timer with OnUnitActiveSec=30s.
  3. Enable: sudo systemctl daemon-reload && sudo systemctl enable --now lab-timer.timer.
  4. Watch: tail -f /tmp/lab-timer.log.
  5. After 2 minutes, verify 3–4 entries in the log.

Lab 2 — Calendar Timer (5 minutes)

  1. Create /etc/systemd/system/lab-calendar.timer with OnCalendar=*:0/1 (every minute).
  2. Create a matching service that writes to /tmp/lab-calendar.log.
  3. Validate: systemd-analyze calendar --iterations=3 '*:0/1'.
  4. Enable and wait 3 minutes.
  5. Check: wc -l /tmp/lab-calendar.log should show ~3 lines.

Lab 3 — Persistent Catch-Up (5 minutes)

  1. Create a timer with OnCalendar=*:0/2 and Persistent=true.
  2. Enable it and let it fire 2 times.
  3. Stop the timer: sudo systemctl stop lab-persistent.timer.
  4. Wait 5 minutes (let 2 runs be "missed").
  5. Start the timer again: sudo systemctl start lab-persistent.timer.
  6. Check logs — the service should fire immediately to catch up.

Lab 4 — Validate Expressions (5 minutes)

Practice validating these. Predict the output before running:

systemd-analyze calendar --iterations=3 '*-*~1 00:00:00'
systemd-analyze calendar --iterations=3 'Mon *-*-1..7 03:00:00'
systemd-analyze calendar --iterations=5 '*-*-* 08..20:0/10'
systemd-analyze calendar --iterations=4 '09,21:00'

Lab 5 — Cleanup

cleanup-labs.sh
for unit in lab-timer lab-calendar lab-persistent; do
sudo systemctl disable --now "${unit}.timer" 2>/dev/null || true
sudo rm -f "/etc/systemd/system/${unit}.timer" "/etc/systemd/system/${unit}.service"
done
sudo systemctl daemon-reload
rm -f /tmp/lab-timer.log /tmp/lab-calendar.log /tmp/lab-persistent.log

Build Task — Complete WordPress Maintenance Timer Set

Create four timer/service pairs that run:

  • DB backup daily at 2:30 AM with flock and RuntimeMaxSec
  • Backup purge daily at 4:00 AM
  • Disk usage alert every 10 minutes
  • Object cache flush every 6 hours

All must use Persistent=true and have security hardening.

Solution
wp-db-backup.timer
[Unit]
Description=WordPress DB backup daily at 02:30

[Timer]
OnCalendar=02:30
Persistent=true
RandomizedDelaySec=5m

[Install]
WantedBy=timers.target
wp-db-backup.service
[Unit]
Description=WordPress database export

[Service]
Type=oneshot
User=www-data
ExecStart=/usr/bin/flock -n /var/lock/wp-db.lock /usr/local/bin/wp db export /mnt/backups/wp-db.sql --path=/var/www/html
RuntimeMaxSec=1h
StandardOutput=append:/var/log/wp-db-backup.log
StandardError=append:/var/log/wp-db-backup.log
NoNewPrivileges=true
PrivateTmp=true
backup-prune.timer
[Unit]
Description=Purge old backups daily at 04:00

[Timer]
OnCalendar=04:00
Persistent=true

[Install]
WantedBy=timers.target
backup-prune.service
[Unit]
Description=Delete backup files older than 14 days

[Service]
Type=oneshot
ExecStart=/usr/bin/find /mnt/backups -name "*.sql" -mtime +14 -delete
StandardOutput=append:/var/log/backup-prune.log
NoNewPrivileges=true
PrivateTmp=true
disk-alert.timer
[Unit]
Description=Check disk usage every 10 minutes

[Timer]
OnCalendar=*:0/10
Persistent=true

[Install]
WantedBy=timers.target
disk-alert.service
[Unit]
Description=Disk usage alert

[Service]
Type=oneshot
ExecStart=/usr/local/bin/disk-alert.sh
StandardOutput=append:/var/log/disk-alert.log
NoNewPrivileges=true
PrivateTmp=true
wp-cache-flush.timer
[Unit]
Description=Flush WP object cache every 6 hours

[Timer]
OnCalendar=*-*-* 00/6:00:00
Persistent=true

[Install]
WantedBy=timers.target
wp-cache-flush.service
[Unit]
Description=Flush WordPress object cache

[Service]
Type=oneshot
User=www-data
ExecStart=/usr/local/bin/wp cache flush --path=/var/www/html
StandardOutput=append:/var/log/wp-cache-flush.log
NoNewPrivileges=true
PrivateTmp=true
enable-all.sh
sudo systemctl daemon-reload
sudo systemctl enable --now wp-db-backup.timer backup-prune.timer disk-alert.timer wp-cache-flush.timer
systemctl list-timers --no-pager | grep -E 'wp-|backup-|disk-'

Mini Quiz

Questions

  1. What two unit files make up a complete systemd timer workflow?

  2. What is the difference between OnCalendar= and OnUnitActiveSec=?

  3. What does Persistent=true do, and which timer type does it affect?

  4. Why use RandomizedDelaySec= for multi-server fleets?

  5. Which command validates the next 5 fire times for a calendar expression?

  6. What command must you run after creating or editing any unit file?

  7. How do you inspect logs from a timer-triggered service?

  8. Why is Type=oneshot the recommended service type for timer jobs?

  9. What is the safe overlap prevention pattern for long-running jobs?

  10. How do you make user timers continue running when the user is logged out?


Answers
  1. A .timer unit (defines when to run) and a .service unit (defines what to run).

  2. OnCalendar= fires at wall-clock times (like cron) — e.g., "every day at 2:15 AM." OnUnitActiveSec= fires at fixed intervals measured from the last timer activation — e.g., "every 60 seconds."

  3. Persistent=true stores the last trigger time on disk. After a reboot or downtime, any missed calendar runs are caught up at the next timer evaluation. It only affects OnCalendar= timers — not monotonic timers.

  4. To prevent synchronized load spikes. Without jitter, 10 servers all scheduled at 02:15 hit the backup target simultaneously. RandomizedDelaySec=5m spreads them across a 5-minute window.

  5. systemd-analyze calendar --iterations=5 'EXPRESSION' — for example: systemd-analyze calendar --iterations=5 'Mon..Fri 06:00'.

  6. sudo systemctl daemon-reload — without this, systemd uses the old (or non-existent) version of the unit file.

  7. journalctl -u myjob.service -n 50 --no-pager for recent logs, or journalctl -u myjob.service -f for live output.

  8. Type=oneshot tells systemd the process is finite — it runs once and exits. This is the correct model for scheduled jobs. systemd won't start a second instance while the first is running.

  9. flock -n + RuntimeMaxSec=:

    ExecStart=/usr/bin/flock -n /var/lock/myjob.lock /usr/local/bin/myjob.sh
    RuntimeMaxSec=2h

    flock -n prevents overlap. RuntimeMaxSec kills runaway jobs.

  10. sudo loginctl enable-linger "$USER" — this allows the user's systemd instance (and their timers) to keep running after logout.


What's Next

You've completed the full systemd timer documentation. Here are related topics: