- systemd-Timer sind ein praktischer Ersatz für
cron: Sie führen Units wie .service nach Zeitplan aus und machen Verlauf, Ausgabe und Umgebungsverwaltung klarer nachvollziehbar.
- Traditionelles
cron hat Schwächen wie ein mehrdeutiges $PATH, leicht verlorenes stdout/stderr, schwer nachzuverfolgende Ausführungshistorien und eine schlecht lesbare Zeitplan-Syntax.
- Timer verknüpfen eine
.timer- und .service-Datei mit demselben Stammnamen und drücken zeit- oder ereignisbasierte Ausführung mit OnCalendar, OnBootSec und OnUnitActiveSec aus.
- Mit
systemd-analyze calendar und systemctl list-timers lassen sich Zeitangaben und der nächste Ausführungszeitpunkt prüfen; WakeSystem= kann die Ausführung sogar aus dem Ruhezustand heraus aufwecken.
RandomizedOffsetSec und FixedRandomDelay= reduzieren gleichzeitige Lastspitzen, und Persistent= holt verpasste Läufe direkt nach dem Wieder-Online-Gehen nach.
Warum man systemd-Timer als Ersatz für cron verwendet
- Der Begriff
cron job wird allgemein verwendet, um einen grundlegenden Computing-Baustein zu bezeichnen, der Aufgaben nach einem Zeitplan ausführt — etwa „führe das jeden Tag aus“ oder „führe jenes jeden Monat aus“ — auch wenn nicht der eigentliche cron-Daemon dahintersteht.
- Ein systemd-Timer ist eine systemd-Unit, die nach einem bestimmten Zeitplan eine andere Unit ausführt, meist eine
.service, und kann ein funktionaler Ersatz für den traditionellen cron-Daemon sein.
- Traditionelles
cron hat einige praktische Schwächen:
- Wegen einer mehrdeutigen
$PATH-Konfiguration ist das Ergebnis der Skriptausführung schwer vorherzusagen.
stdout- und stderr-Ausgaben verschwinden oft im Nirgendwo oder werden an das Mailsystem des Hosts geschickt.
- Die Ausführungshistorie ist schwer nachzuverfolgen und abzufragen.
- Eine Zeitplan-Syntax wie
01,31 04,05 1-15 1,6 * ist für Menschen weder gut lesbar noch intuitiv.
- systemd-Timer mindern diese Probleme und bieten zugleich Kalendereinstellungen, die
cron-artigen Ausdrücken ähneln.
Grundstruktur: Service und Timer
- Ein systemd-Timer braucht ein Ausführungsziel, und eine
.service-Unit kann logisch wie ein Skript betrachtet werden.
- Legt man zum Beispiel unter
/etc/systemd/system/roulette.service die folgende Unit ab, installiert man einen Service, der den Computer mit einer Wahrscheinlichkeit von eins zu zehn herunterfährt.
[Unit]
Description=1 in 10 chance to break your chains
[Service]
ExecStart=/usr/bin/env bash -c '[[ $(($RANDOM % 10)) == 0 ]] && systemctl poweroff || echo LIVE ANOTHER DAY'
ExecCondition= ist eine stärker integrierte Art, bedingte Ausführung als systemd-Service-Option auszudrücken, und macht auf Unit-Ebene klarer sichtbar: „Soll weiter ausgeführt werden?“
[Unit]
Description=1 in 10 chance to break your chains
[Service]
ExecCondition=/run/current-system/sw/bin/bash -c '[[ $(($RANDOM % 10)) == 0 ]]'
ExecStart=/run/current-system/sw/bin/systemctl poweroff
- Wenn die Bedingung nicht erfüllt ist, erscheint im Journal eine klarere Meldung.
May 05 11:05:32 diesel systemd[3117]: Condition check resulted in 1 in 10 chance to break your chains being skipped.
- Generell ist es oft angenehmer, die von systemd bereitgestellten Optionen zu nutzen, statt selbst zu skripten.
OnFailure= kann verwendet werden, um auf fehlschlagende Service-Skripte zu reagieren.
Restart= kann verwendet werden, um bei vorübergehenden Fehlern eine Wiederherstellung zu versuchen.
Verknüpfen und Ausführen von Timer-Units
- Legt man
/etc/systemd/system/roulette.timer mit demselben Dateistamm an, kann man den Timer mit roulette.service verknüpfen.
[Unit]
Description=impending destruction
[Timer]
OnCalendar=10:00
[Install]
WantedBy=timers.target
- Standardmäßig wählt die
Unit=-Einstellung des Timers die Service-Unit mit demselben Stammnamen und der Endung .service.
- In diesem Beispiel wird
roulette.service gewählt.
- Soll eine Service-Unit mit anderem Namen ausgeführt werden, kann
Unit= geändert werden.
- Das Ziel von
ExecStart= wird standardmäßig nicht als Shell-Befehl ausgeführt.
- Ein Ziel mit absolutem Pfad muss als Skript oder als Interpreter behandelt werden, der ein Skript als String-Argument erwartet.
ExecStart=/usr/bin/echo Hello | /usr/bin/awk funktioniert in diesem Kontext nicht, weil die Pipe hier keine Bedeutung hat.
ExecStart=-Argumente erben standardmäßig keine Umgebungsvariablen außer einigen Vorgaben des Systemmanagers.
- Das Standard-
$PATH ist fast leer.
- Das Ausführen von
/usr/bin/env ist eine einfache Schutzmaßnahme, damit Dinge wie systemctl verfügbar sind.
- Selbst wenn
ExecStart=/usr/bin/bash allein ein Standard-$PATH mitbringen würde, ist die Verwendung von env eine zusätzliche Absicherung.
- Ein Service kann auch direkt ohne Timer ausgeführt werden.
systemctl start roulette
- Ein Service ohne
[Install]-Abschnitt kann nicht mit enable aktiviert werden; in dieser Struktur ist der Timer der Standardweg, um den Service konsistent auszuführen.
systemctl arbeitet standardmäßig mit roulette.service, auch wenn kein explizites Suffix angegeben wird.
- Wenn man
systemctl start auf eine .timer-Unit anwendet, wird der Timer aktiv, aber der eigentliche mit Unit= angegebene Service wird nicht sofort ausgeführt.
systemctl start roulette.timer
status zeigt an, wann der Timer das nächste Mal ausführt.
systemctl status roulette.timer
Trigger: Sat 2026-04-18 10:00:00 MDT; 35min left
- Der einfachste Ablauf ist: den auszuführenden Service anlegen, einen Timer mit Zeitplan am selben Ort ablegen und dann den Timer statt des Ziels starten.
- Wenn der
[Install]-Abschnitt der Timer-Unit WantedBy= enthält, kann der Timer auch beim Booten automatisch hochkommen.
systemctl enable roulette.timer
Zeitausdrücke: Kalenderereignisse und Zeitspannen
- Bei Timern ist wichtig, wie Zeitpläne ausgedrückt werden: Man sollte zwischen wiederkehrenden Zeitintervallen und Kalenderereignissen oder Zeitstempeln unterscheiden.
- Die Handbuchseite
systemd.time(7) enthält genügend Beispiele und ist eine gute erste Referenz beim Schreiben von Timern.
systemd-analyze kann Zeitausdrücke prüfen und erläutern.
systemd-analyze calendar '*-*-* *:*:*'
Normalized form: *-*-* *:*:*
Next elapse: Sat 2026-04-18 16:44:26 MDT
(in UTC): Sat 2026-04-18 22:44:26 UTC
From now: 431ms left
- systemd-Timer können nicht nur wiederkehrende Wanduhrzeiten definieren, sondern im Unterschied zu traditionellem
cron auch wiederkehrende Zeitspannen relativ zu einem früheren Ereignis.
- Die vollständige Form von
daily bedeutet: jedes Jahr, jeden Monat, jeden Tag um 00:00:00 ausführen.
*-*-* 00:00:00
│ │ │ │ │ ╰── at second 00
│ │ │ │ ╰───── at minute 00
│ │ │ ╰──────── at hour 00
│ │ ╰────────── every day
│ ╰──────────── every month
╰────────────── every year
- Man kann Kurzformen wie
daily, die vollständige Form und andere von systemd.time(7) unterstützte Werte verwenden und seine Annahmen mit systemd-analyze überprüfen.
Wenn ereignisbasierte Ausführung besser passt
- In der Praxis passt „nach einem anderen Ereignis ausführen“ oft besser als „jeden Tag zur gleichen Uhrzeit ausführen“.
- Beim Leeren eines temporären Verzeichnisses gibt es in
/tmp kurz nach dem Booten vielleicht kaum etwas aufzuräumen, wenn der cron-Zeitpunkt bereits verstrichen ist.
- Die Formulierung „eine Stunde nach dem Start des Computers ausführen und danach stündlich“ passt oft besser zum tatsächlichen Verhalten des Services und zur Logik des Zeitplans.
[Timer]
OnBootSec=1h
OnUnitActiveSec=1h
OnBootSec=1h bedeutet: einmal eine Stunde nach dem Start der Maschine ausführen.
OnUnitActiveSec=1h bedeutet: eine Stunde nach der Ausführung von Unit= erneut ausführen, wodurch der Timer implizit dauerhaft wiederholt wird.
- Solche periodischen Zeitspannen passen häufiger zu „ab und zu mal ausführen“ als Ausdrücke wie „jede Stunde zu dieser Minute“.
- Beim Beispiel eines Slack-Bots, der die Advent of Code-API pollt, hält der
cron-Ausdruck */15 zwar die Richtlinie „alle 15 Minuten“ der API ein, aber wenn alle auf dieselbe Weise pollen, kann sich der Traffic ballen.
- Wenn man nach einer Codeänderung den Timer startet und ihn dann jeweils 15 Minuten später ausführen lässt, erfüllt man das gewünschte Verhalten und reduziert womöglich das thundering herd-Problem.
Timer-Status auf einen Blick
systemctl list-timers ist ein High-Level-Befehl, der die Timer-Situation auf einem System zusammenfasst.
systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2026-04-20 15:15:00 MDT 1min 40s Mon 2026-04-20 15:00:05 MDT 13min ago zfs-snapshot-frequent.timer zfs-snapshot-frequent.service
Mon 2026-04-20 15:32:16 MDT 18min Mon 2026-04-20 14:22:15 MDT 51min ago fwupd-refresh.timer fwupd-refresh.service
Mon 2026-04-20 16:00:00 MDT 46min Mon 2026-04-20 15:00:05 MDT 13min ago logrotate.timer logrotate.service
Mon 2026-04-20 16:00:00 MDT 46min Mon 2026-04-20 15:00:05 MDT 13min ago zfs-snapshot-hourly.timer zfs-snapshot-hourly.service
Tue 2026-04-21 00:00:00 MDT 8h Mon 2026-04-20 09:43:22 MDT 5h 29min ago zfs-snapshot-daily.timer zfs-snapshot-daily.service
Tue 2026-04-21 07:31:28 MDT 16h Sun 2026-04-19 20:15:47 MDT 7h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2026-04-27 00:00:00 MDT 6 days Mon 2026-04-20 09:43:22 MDT 5h 29min ago zfs-snapshot-weekly.timer zfs-snapshot-weekly.service
Mon 2026-04-27 01:09:27 MDT 6 days Mon 2026-04-20 09:43:22 MDT 5h 29min ago fstrim.timer fstrim.service
Mon 2026-04-27 04:28:38 MDT 6 days Mon 2026-04-20 09:43:22 MDT 5h 29min ago zpool-trim.timer zpool-trim.service
Fri 2026-05-01 00:00:00 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-snapshot-monthly.timer zfs-snapshot-monthly.service
Fri 2026-05-01 03:17:17 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-scrub.timer zfs-scrub.service
11 timers listed.
Pass --all to see loaded but inactive timers, too.
- Mit einem einzigen Befehl erhält man einen Gesamtüberblick über alle Elemente, die nach Timer-Zeitplänen laufen.
list-timers gehört zu einer häufig genutzten Familie von systemd-Unterbefehlen.
list-units ist ebenfalls nützlich.
list-paths ist ein neuerer Unterbefehl, der systemctl hinzugefügt wurde.
Aus dem Ruhezustand aufwecken und ausführen
WakeSystem= kann dafür sorgen, dass ein abgelaufener Timer das System aus dem Ruhezustand aufweckt.
WakeSystem=
Takes a boolean argument. If true, an elapsing timer will
cause the system to resume from suspend, should it be
suspended and if the system supports this.
...
- Das ist nützlich, wenn ein wichtiges Skript laufen soll, ohne dass jemand physisch den Laptopdeckel öffnen muss.
- Auf Distributionen wie Arch oder NixOS, die das Herunterladen von Paket-Updates vor der eigentlichen Nutzung unterstützen, kann man spät nachts Update-Pakete vorab holen und sie morgens an der Tastatur installieren.
- Laut Handbuch muss man das erneute Versetzen in den Ruhezustand manuell veranlassen, wenn das System nach Abschluss der
.service wieder schlafen soll.
Ausführungszeiten streuen und thundering herd entschärfen
- Das thundering herd-Problem ist ein Systemproblem, das auftritt, wenn viele Prozesse gleichzeitig aufwachen.
- Wenn weltweit alle Debian-Systeme hart auf
00:00:00 für apt update eingestellt wären, wäre Mitternacht für alle eine schlechte Traffic-Spitzenzeit.
FixedRandomDelay= und RandomizedOffsetSec= helfen dabei, Ausführungszeiten zu verteilen.
FixedRandomDelay=
Takes a boolean argument. When enabled, the randomized delay
specified by RandomizedDelaySec= is chosen deterministically,
and remains stable between all firings of the same timer,
even if the manager is restarted. ...
RandomizedOffsetSec=
Offsets the timer by a stable, randomly-selected, and evenly
distributed amount of time between 0 and the specified time
value. ...
- Solche Einstellungen können auf realen Systemen verwendet werden, die nach Software-Updates suchen.
- Eine gleichmäßige Verteilung der Ausführung hilft, das thundering herd-Problem zu reduzieren, das Verhalten konsistent zu halten und Störungen wie Daemon-Neustarts während der Koordination verteilter Dienste zu vermeiden.
- Insgesamt sind die Timing-Optionen sehr gut konfigurierbar und bieten feingranulare Kontrolle.
Verpasste Ausführungen sofort nachholen
Persistent= eignet sich besonders für geplante Skripte, die wegen eines schlafenden Laptops nicht verpasst werden sollten, für die aber WakeSystem= nicht nötig ist.
Persistent=
Takes a boolean argument. If true, the time when the service
unit was last triggered is stored on disk. When the timer is
activated, the service unit is triggered immediately if it
would have been triggered at least once during the time when
the timer was inactive. ...
- Wenn ein für Konfigurationsmanagement geplantes Check-in-System Ausfallzeit hatte, kann schon
Persistent= in der .timer genügen, damit es direkt nach dem Wieder-Online-Gehen in den richtigen Zustand konvergiert.
- Ohne
Persistent= muss man womöglich bis zum nächsten regulären Ausführungszeitpunkt des Timers warten, und das kann lange dauern.
- Weitere Aufgaben, bei denen man bei erkannter verpasster Aktivierung nicht warten sollte, sind System-Updates oder die Prüfung von Batch-Jobs.
Worauf man beim Schreiben von Timern achten sollte
- Timer im Kontext des Benutzer-Managers, die mit
systemctl --user verwaltet werden, sind ebenfalls sinnvoll, aber man sollte auf das Ziel im [Install]-Abschnitt achten.
- Je nach Distribution kann für Benutzer-Timer
default.target das passende Ziel sein.
- Wie bei
cron gilt weiterhin der allgemeine Hinweis, dass eine korrekte Systemzeit wichtig ist.
- systemd-Nutzer können den Synchronisationsstatus mit
timedatectl timesync-status prüfen.
- Viele Editoren unterstützen das Format von systemd-Unit-Dateien direkt, was bei größeren Unit-Dateien hilfreich ist.
- In Emacs kann man das Paket emacs systemd verwenden.
1 Kommentare
Lobste.rs-Meinungen
systemd ist zwar nicht perfekt, aber viele Designentscheidungen wirken so, als würden sie auf Erkenntnissen aus traditionelleren früheren Ansätzen beruhen
Ich habe mir neulich noch einmal die Episode von CRE aus dem Jahr 2015 angehört, in der Lennart Poettering den Hintergrund erklärt, und kann sie immer noch empfehlen
Ich gehöre eher zur Fraktion, die systemd durch und durch nicht mag, aber systemd.timers halte ich für eines der „weniger schlechten“ Konzepte dieses Produkts
Deshalb war ich etwas überrascht, dass der Autor es auf eine Weise verteidigt hat, die Leute mit berechtigten Beschwerden herabsetzt
Trotzdem ist die Nutzung zusammen mit dem
at-Befehl gut. Für Befehle, die einmal zu einem bestimmten Zeitpunkt ausgeführt werden sollen, nutze ichat, und für alles andere systemd-Timer mit einfachen Unit-DateienDie Verbesserung, die ich am liebsten sehen würde, wäre, erkennen zu können, welcher Benutzer einen Timer ausführt. Ich bin zwar einer der wenigen Leute, die 2026 noch eine Shellbox betreiben, aber es wäre nützlich zu wissen, welcher Benutzer einen Timer eingerichtet hat, der jede Sekunde auf die Festplatte hämmert
Soweit ich weiß, kann es mit
loginctl enable-lingerauch ohne aktive Benutzersitzung laufen. Natürlich gibt es sicher Anwendungsfälle, für die das nicht ausreicht, aber ich kenne die konkrete Situation nichtBei systemd-Timern wäre es besonders auf der Seite der Benutzerverwaltung schön, wenn die Einstiegshürde niedriger wäre
Wenn man sieht, wie viel Konfiguration nötig ist, ist
crontab -ewirklich nur schwer zu schlagenIch habe lange darüber nachgedacht, wie man Logs von cron-Skripten systematisch sammeln kann, und dann gemerkt, dass man einfach systemd-Timer verwenden kann
Das Logging-Problem ist damit gelöst. Jetzt habe ich keinen Grund mehr, wieder cron zu verwenden, und wünschte, ich hätte das früher gewusst
loggerweiterleiten oder mit>>an eine Logdatei anhängen oder die Standardeinstellung beibehalten und E-Mails empfangen?Nennt es ruhig altmodisch, aber auf Servern richte ich mir immer noch E-Mail ein, die mich erreicht
Wenn man das automatisiert, ist es auf jedem neuen Host kostenlos mit dabei, und auch im Alltag ziemlich praktisch
Zum Beispiel öffne ich einen Multiplexer, führe
long_running_process | mail root@localhost -s "done $?"aus und vergesse es dann einfachGuter Artikel, und ich hatte selbst einen Entwurf für einen ähnlichen Artikel, auf den ich kürzlich wieder zurückgreifen musste
Wenn man wie ich in den systemd-Kaninchenbau hinabsteigt, empfehle ich, die Unit-Dateien und Timer im zugehörigen Projektordner nach
/etc/systemd/system/symbolisch zu verlinkenEine meiner Beschwerden über systemd ist, dass es nicht zwischen von der Distribution installierten Units und selbst geschriebenen Units unterscheidet, aber mit symbolischen Links kann man diese Trennung selbst aufrechterhalten
System-/Paket-/Distributions-Units liegen unter
/usr/lib/systemd/system, und lokale Overrides oder lokale Units unter/etc/systemd/system