linux Einsteiger 4 Min. Lesezeit

Systemd-Timer im Überblick: Wann lief was zuletzt?

Problem

Auf einem Linux-Server laufen Dutzende systemd-Timer im Hintergrund — Backups, Paket-Updates, Wartungsaufgaben. Wann hat welcher Timer zuletzt ausgeführt? Hat einer versagt? Das mühsam aus 'systemctl list-timers' herauszulesen kostet Zeit.

Lösung

timer-report.sh listet alle systemd-Timer übersichtlich auf: letzter Lauf, nächster Lauf und Ergebnis des zugehörigen Service — farbig, sortiert, rein lesend.

Getestet auf
  • Ubuntu 24.04
  • Debian 12
  • Debian 13
Benötigt
  • bash
  • systemctl

Systemd-Timer sind der moderne Ersatz für Cronjobs — zuverlässiger, besser ins System integriert und mit eingebauter Protokollierung. Auf einem typischen Debian- oder Ubuntu-Server laufen zwischen 8 und 20 Timer gleichzeitig: apt-daily, logrotate, fstrim, eigene Backup-Skripte und mehr.

Das Problem: systemctl list-timers zeigt zwar die nächsten Läufe, aber das Format ist sperrig, die Ergebnisse vergangener Läufe fehlen, und auf einen Blick sehen ob irgendetwas fehlgeschlagen ist — das geht so nicht. journalctl wäre die Alternative, aber dort muss man für jeden Timer einzeln suchen.

timer-report.sh löst das in einer Ausgabe: alle Timer, ihr letzter Lauf, ihr nächster Lauf und das Ergebnis — farbig hervorgehoben, damit Probleme sofort ins Auge springen.

Das Problem

Wer regelmäßig Server betreut, kennt die Situation: Ein Backup-Script meldet sich nicht mehr, ein Zertifikat läuft ab, ein Wartungsjob schlägt still fehl. systemctl list-timers zeigt nur die nächsten geplanten Läufe — aber nicht ob der letzte Lauf überhaupt geklappt hat.

Das manuelle Durchsuchen von journalctl -u backup.service -n 20 für jeden einzelnen Service ist zeitaufwendig. Besonders auf Homelab-Servern, wo man nicht täglich reinschaut, sammeln sich unbemerkte Fehler an.

Die Lösung

timer-report.sh fragt alle Informationen direkt über systemctl show ab — ohne Log-Parsing, ohne externe Tools, ohne Root-Rechte. Die Ausgabe ist tabellarisch, farbcodiert (grün = ok, gelb = nie gelaufen, rot = Fehler) und passt in ein Standard-Terminal.

Voraussetzungen

Das Script benötigt nur bash und systemctl — beides ist auf jedem modernen Debian/Ubuntu standardmäßig vorhanden. Root-Rechte sind nicht nötig, um System-Timer zu lesen.

  • Bash 4.0 oder neuer
  • systemd (getestet auf systemd 252+)

Verwendung

# System-Timer anzeigen (Standard)
bash timer-report.sh

# Nur fehlgeschlagene Timer anzeigen
bash timer-report.sh --failed

# Timer des aktuellen Benutzers anzeigen
bash timer-report.sh --user

# Hilfe
bash timer-report.sh --help

Das Script ändert nichts am System — es liest ausschließlich.

Zeile für Zeile erklärt

set -euo pipefail Bricht sofort bei jedem Fehler ab; ungesetzte Variablen sind Fehler
if [[ -t 1 ]]; then ... fi Farben nur wenn die Ausgabe ein Terminal ist — sauber in Logs und Pipes
SCOPE=(--system) Standard: System-Timer; --user wechselt auf Timer des aktuellen Nutzers
mapfile -t TIMERS < <(sc list-units --type=timer ...) Liest alle Timer-Namen in ein Array — robuster als Zeilenverarbeitung mit for
last=$(sc show -p LastTriggerUSec --value) Letzter Auslöse-Zeitstempel direkt aus systemd (präzise, kein Log-Parsing nötig)
next=$(sc show -p NextElapseUSecRealtime --value) Nächster geplanter Lauf in Realtime; Fallback auf Monotonic für Boot-relative Timer
last_fmt=$([[ "$last" == "—" ]] && echo ...) Systemd gibt Datum mit Wochentag-Präfix aus (z.B. 'Tue 2026-11-12'); ab Zeichen 4 ist das eigentliche Datum
[[ $bad -gt 0 ]] && echo Tipp: journalctl ... Zeigt den Diagnose-Hinweis nur wenn wirklich etwas fehlgeschlagen ist

Komplettes Script timer-report.sh

#!/usr/bin/env bash
# timer-report.sh — Systemd-Timer übersichtlich: zuletzt, als Nächstes, Ergebnis
set -euo pipefail

if [[ -t 1 ]]; then
  R=$'\e[31m'; G=$'\e[32m'; Y=$'\e[33m'; C=$'\e[36m'; B=$'\e[1m'; N=$'\e[0m'
else
  R=""; G=""; Y=""; C=""; B=""; N=""
fi

usage() {
  cat <<EOF
${B}timer-report.sh${N} — zeigt alle systemd-Timer mit letztem Lauf, nächstem Lauf
und Ergebnis des zugehörigen Services. Rein lesend, ändert nichts.

Nutzung: bash timer-report.sh [--user] [--failed] [--help]
  --user     Timer des aktuellen Benutzers statt System-Timer anzeigen
  --failed   Nur Timer zeigen, deren Service fehlschlug oder nie lief
  --help     Diese Hilfe anzeigen
EOF
}

SCOPE=(--system); ONLY_BAD=0
for arg in "$@"; do
  case "$arg" in
    --help|-h) usage; exit 0 ;;
    --user)    SCOPE=(--user) ;;
    --failed)  ONLY_BAD=1 ;;
    *) echo "${R}Unbekannte Option: $arg${N}" >&2; usage >&2; exit 2 ;;
  esac
done

command -v systemctl >/dev/null || { echo "${R}systemctl nicht gefunden.${N}" >&2; exit 1; }
sc() { systemctl "${SCOPE[@]}" "$@" 2>/dev/null || true; }

mapfile -t TIMERS < <(sc list-units --type=timer --all --no-legend --plain | awk '{print $1}')
if [[ ${#TIMERS[@]} -eq 0 ]]; then
  echo "${Y}Keine Timer gefunden.${N}"; exit 0
fi

printf '%s%-32s %-21s %-21s %s%s\n' "$B" "TIMER" "ZULETZT" "NÄCHSTER LAUF" "ERGEBNIS" "$N"
ok=0; bad=0; never=0
for t in "${TIMERS[@]}"; do
  svc=$(sc show -p Unit --value "$t")
  last=$(sc show -p LastTriggerUSec --value "$t")
  next=$(sc show -p NextElapseUSecRealtime --value "$t")
  result=$(sc show -p Result --value "$svc")
  [[ -z "$last" || "$last" == "n/a" || "$last" == "infinity" ]] && last="—"
  [[ -z "$next" || "$next" == "n/a" || "$next" == "infinity" ]] && next=$(systemctl "${SCOPE[@]}" show -p NextElapseUSecMonotonic --value "$t" 2>/dev/null || true)
  [[ -z "$next" || "$next" == "n/a" || "$next" == "infinity" ]] && next="—"
  if [[ "$last" == "—" ]]; then
    status="${Y}nie gelaufen${N}"; never=$((never + 1))
  elif [[ "$result" == "success" ]]; then
    status="${G}ok${N}"; ok=$((ok + 1))
    [[ $ONLY_BAD -eq 1 ]] && continue
  else
    status="${R}${result:-unbekannt}${N}"; bad=$((bad + 1))
  fi
  last_fmt=$([[ "$last" == "—" ]] && echo "—" || echo "${last:4:16}")
  next_fmt=$([[ "$next" == "—" ]] && echo "—" || echo "${next:4:16}")
  printf '%s%-32s%s %-21s %-21s %s\n' "$C" "${t:0:32}" "$N" "$last_fmt" "$next_fmt" "$status"
done

echo
echo "${B}Zusammenfassung:${N} ${G}${ok} ok${N} · ${R}${bad} fehlgeschlagen${N} · ${Y}${never} nie gelaufen${N}"
if [[ $bad -gt 0 ]]; then
  echo "Tipp: Details mit ${C}journalctl -u <service> -n 50${N} ansehen."
fi
exit 0