Fehlerbehebung · systemd

systemd-Timer läuft nicht? In fünf Schritten diagnostizieren

Warum ein systemd-Timer nicht auslöst — nicht aktiviert, OnCalendar-Syntax, fehlendes daemon-reload, fehlschlagende Service-Unit, User-Timer ohne Lingering — und die jeweilige Lösung.

systemd-Timer sind der moderne Cron-Ersatz unter Linux — mit besserem Logging und mehr Fehlermodi, die man kennen sollte. Arbeiten Sie die Punkte der Reihe nach ab.

Diagnose in 30 Sekunden:

SymptomWahrscheinlichste UrsacheAbschnitt
Timer fehlt in list-timers, oder NEXT ist n/aNicht aktiviert oder kaputtes OnCalendar§1 / §3
Änderungen scheinen wirkungslosFehlendes daemon-reload§2
LAST zeigt einen Lauf, aber nichts passierteDie zugehörige Service-Unit schlug fehl§4
Funktioniert, stoppt dann zufälligUser-Timer ohne Lingering§5
Läuft Minuten später als geplantAccuracySec / RandomizedDelaySec§5

1. Ist der Timer überhaupt aktiviert und gestartet?

Der Klassiker: Die Unit-Dateien existieren, aber niemand hat den Timer gestartet.

systemctl list-timers --all          # ist Ihr Timer gelistet? NEXT gesetzt?
systemctl status backup.timer        # "Active: active (waiting)" ist das Ziel
sudo systemctl enable --now backup.timer

enable allein startet erst beim nächsten Boot; enable --now sofort. Zeigt NEXT den Wert n/a, ist der Timer geladen, hat aber keinen gültigen Trigger — meist ein Syntaxproblem (§3).

Zwei verwandte Fallen:

  • Der Timer ist aktiviert, aber die Unit-Datei hat keine [Install]-Sektion — dann bewirkt enable nichts Sinnvolles. Ein Timer braucht WantedBy=timers.target unter [Install].
  • Der Timer wurde maskiert (systemctl status zeigt masked) — typisch durch Paketmanager oder Admins. Erst systemctl unmask backup.timer.

2. Haben Sie nach dem Editieren daemon-reload ausgeführt?

systemd liest Unit-Dateien einmal. Änderungen an backup.timer oder backup.service bewirken nichts, bis:

sudo systemctl daemon-reload
sudo systemctl restart backup.timer

Am härtesten trifft das Konfigurationsmanagement (Ansible, Puppet), das Unit-Dateien schreibt, aber kein Reload auslöst — Datei und geladene Unit divergieren still. systemctl show backup.timer -p NeedDaemonReload verrät, ob die geladene Version veraltet ist.

3. Den OnCalendar-Ausdruck validieren

OnCalendar ist keine Cron-Syntax. 0 3 * * * bedeutet stillschweigend etwas anderes oder parst gar nicht. Das Format ist Wochentag Jahr-Monat-Tag Stunde:Minute:Sekunde:

[Timer]
OnCalendar=*-*-* 03:00:00        # täglich um 03:00
OnCalendar=Mon *-*-* 09:00:00    # montags um 09:00
OnCalendar=*-*-* *:0/15:00       # alle 15 Minuten
OnCalendar=Sat,Sun *-*-* 10:00   # am Wochenende um 10:00

Immer mit dem eingebauten Validator prüfen — er zeigt auch die nächste Auslösung:

systemd-analyze calendar "Mon *-*-* 09:00:00"

Failed to parse calendar specification in journalctl -u backup.timer heißt: Der Timer hat nie etwas geplant. Beim Übersetzen aus einer Crontab hilft die Zeitplan-Referenz — übersetzen Sie die Bedeutung, nicht die Syntax.

Timer verwenden außerdem standardmäßig die System-Zeitzone (anders als GitHub Actions oder Vercel, die UTC erzwingen) — relevant, wenn eine auf dem Laptop geschriebene Unit auf einen UTC-Server deployt wird.

4. Der Timer hat ausgelöst — aber der Service schlug fehl

Ein aktuelles LAST in list-timers beweist nur, dass der Timer funktioniert. Die Arbeit passiert in der zugehörigen Service-Unit (backup.timerbackup.service, außer Unit= zeigt woanders hin):

systemctl status backup.service      # Result: exit-code? oom-kill?
journalctl -u backup.service -n 50   # die eigentliche Ausgabe und Fehler

Die üblichen Verdächtigen, nach Häufigkeit:

  • Relative Pfade — Units laufen von /, nicht aus Ihrem Home. Absolute Pfade in ExecStart und für jede Datei, die das Skript anfasst.
  • Fehlende Umgebung — Ihr Shell-Profil wird nicht geladen. PATH ist minimal; nvm-, pyenv-, rbenv-Shims und exportierte Secrets existieren nicht. Environment= / EnvironmentFile= in der Unit setzen.
  • Berechtigungen — der Service läuft als User= (Standard: root bei System-Units); Dateien Ihres interaktiven Users sind evtl. nicht les-/schreibbar.
  • Type=-Mismatch — ein Skript, das mit Type=oneshot daemonisiert (oder umgekehrt), lässt systemd den Prozess killen oder falsch verfolgen.

5. User-Timer stoppen beim Logout

Ein mit systemctl --user installierter Timer läuft nur, solange Sie eine Session haben — außer Lingering ist aktiviert:

loginctl enable-linger $USER

Ohne stirbt Ihr Timer beim Logout und läuft erst beim nächsten Login weiter — das sieht exakt aus wie „stoppt zufällig“.

Außerdem gut zu wissen:

  • Persistent=true holt einen verpassten Zeitplan beim nächsten Boot nach — essenziell für Maschinen, die nicht durchlaufen. Ohne überspringt ein 03:00-Timer auf einem schlafenden Laptop einfach.
  • AccuracySec ist standardmäßig 1 Minute, und RandomizedDelaySec fügt Jitter hinzu — beide lassen Läufe später als die wörtliche OnCalendar-Zeit starten. Für Pünktlichkeit AccuracySec=1s.
  • WakeSystem=true kann eine schlafende Maschine für den Timer wecken — Standard ist, bis zum nächsten Aufwachen zu warten.

Das eigentliche Problem: Das Journal ruft Sie nicht an

Alles oben landet in journalctl — genau dort, wo niemand hinschaut, bis etwas wochenlang kaputt war. Ein Timer, der aufhört zu planen, ein Service, der zu scheitern beginnt, eine Maschine, die zur Triggerzeit aus war: alles still.

Schließen Sie die Lücke mit einem Heartbeat-Ping bei Erfolg:

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
ExecStartPost=/usr/bin/curl -fsS https://ping.steadycron.com/<ihr-ping-token>

ExecStartPost läuft nur, wenn ExecStart erfolgreich war — ein fehlgeschlagenes Backup, ein toter Timer und eine ausgeschaltete Maschine erzeugen also dasselbe Signal: einen ausgebliebenen Ping und einen Alarm per E-Mail, Slack, Discord oder Telegram innerhalb Ihrer Grace-Zeit. Siehe Heartbeat-Monitoring für Grace-Zeiten und Alert-Routing sowie Ping-Snippets für Wrapper-Muster, die auch Fehler melden (inklusive OnFailure=-Handler).

Häufige Fragen

Warum löst mein systemd-Timer überhaupt nicht aus?

Prüfen Sie die Grundlagen der Reihe nach: Ist der Timer aktiviert und gestartet (systemctl list-timers --all — zeigt NEXT eine Zeit?), haben Sie nach dem Editieren daemon-reload ausgeführt, und akzeptiert systemd-analyze calendar Ihren OnCalendar-Ausdruck? Ein NEXT von n/a bedeutet fast immer ein Kalender-Syntaxproblem.

Kann ich Cron-Syntax wie 0 3 * * * in OnCalendar verwenden?

Nein — OnCalendar ist kein Cron. Das Format ist Wochentag Jahr-Monat-Tag Stunde:Minute:Sekunde, z. B. *-*-* 03:00:00 für täglich um 03:00. Validieren mit systemd-analyze calendar "Ausdruck" — das zeigt auch die nächste Auslösung.

Warum zeigt mein Timer einen LAST-Lauf, aber nichts ist passiert?

Timer und Arbeit sind getrennte Units. LAST beweist nur, dass der Timer ausgelöst hat; die eigentliche Arbeit steckt in der zugehörigen Service-Unit. Prüfen Sie systemctl status ihrjob.service und journalctl -u ihrjob.service — vermutlich schlug der Service fehl (Pfade, Umgebung, Berechtigungen).

Warum stoppt mein systemd --user-Timer beim Logout?

User-Timer laufen nur mit aktiver Session, außer Lingering ist aktiviert: loginctl enable-linger $USER. Ohne stirbt der Timer beim Logout und läuft erst beim nächsten Login weiter — das sieht aus wie zufälliges Stoppen.

Warum läuft mein systemd-Timer jeden Tag zu leicht anderer Zeit?

AccuracySec ist standardmäßig 1 Minute und RandomizedDelaySec fügt bewussten Jitter hinzu — beide lassen Läufe nach der wörtlichen OnCalendar-Zeit starten. Für Präzision AccuracySec=1s setzen; Jitter behalten, wenn viele Maschinen denselben Zeitplan feuern.