Der klassische Cron-Daemon ist unter Linux nach wie vor das Werkzeug der Wahl, wenn es darum geht, einen schnellen Befehl zu einer bestimmten Uhrzeit in die Crontab zu werfen. Wenn ein modernes Linux-System aber ohnehin vollständig von systemd verwaltet wird, sind systemd.timer-Units für wiederkehrende Aufgaben oft die elegantere und robustere Lösung.
Ein großer Vorteil von systemd-Timern liegt im modularen Aufbau: Der Timer selbst übernimmt niemals die eigentliche Aufgabe. Seine einzige Pflicht ist es, eine separate .service-Unit zu triggern, die anschließend den Job ausführt. Zeitplanung und Programmlogik bleiben dadurch getrennt. Der Scheduler ist direkt in die Serviceverwaltung integriert, Jobs haben einen eigenen Unit-Status und die Ausführung landet automatisch im Journal.
Als einfaches Beispiel aus meinem Homelab nutze ich dieses Prinzip beispielsweise, um anstehende Server-Neustarts nach Updates vollautomatisch und ohne Ausfallzeiten in die tiefe Nacht zu verschieben.
Timer und Service
Die Konfiguration besteht aus zwei Units und einem Skript, das anschließend gestartet wird. Die systemd-Units sind in diesem Verzeichnis abzulegen und im INI-Format geschrieben:
/etc/systemd/system/
├── scheduled-reboot.service
└── scheduled-reboot.timer
Timer
Der Timer definiert ausschließlich den Zeitpunkt:
[Unit]
Description=Trigger nächtlichen Reboot bei Bedarf
[Timer]
OnCalendar=*-*-* 03:30:00
[Install]
WantedBy=timers.target
Der [Unit]-Block: bildet den Kopf und Namen jeder systemd-Konfigurationsdatei. Er dient der Identifikation und der Definition von Abhängigkeiten. Description= ist ein frei wählbarer, prägnanter Klartextname. Dieser erscheint später in den System-Logs, was die Identifikation des Timers bei Problemen erheblich erleichtert.
Der [Timer]-Block: Definiert das “Wann”. Hier wird exakt festgelegt, unter welchen Bedingungen und in welchen Intervallen der Timer den gekoppelten Service triggern soll. Die Zeitdefinition: OnCalendar=: Bestimmt den Ausführungszeitpunkt basierend auf Echtzeit-Ereignissen (z. B. *-*-* 03:30:00 für täglich um 03:30 Uhr).
Der [Install]-Block: Das ist der Autostart-Eintrag für den Timer. Er legt fest, wann die Konfiguration aktiv werden soll. WantedBy=timers.target: Sagt dem System: „Sobald der Server hochfährt und die Zeitschaltuhren geladen werden, starte diesen Timer automatisch mit.“ Ohne diesen Eintrag würde der Timer nach einem System-Neustart einfach ausbleiben.
Service
Die eigentliche Aktion wandert in die dazugehörige Service-Unit /etc/systemd/system/scheduled-reboot.service. Natürlich können das bei komplexeren Jobs oder Kaskaden auch mehrere Service Units sein:
[Unit]
Description=Startet das Prüfskript
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/scheduled-reboot
Abhängigkeiten = After=: Hier lässt sich festlegen, welche Systemdienste (wie das Netzwerk oder gemountete Dateisysteme) bereits aktiv sein müssen, bevor dieser Timer überhaupt geladen oder berücksichtigt werden darf!
After=multi-user.target: Das multi-user.target repräsentiert den Zustand, in dem der Server vollständig hochgefahren ist und alle Standard-Dienste (wie SSH oder Docker) aktiv sind. Da das Skript über loginctl prüft, ob noch Benutzer angemeldet sind, macht diese Abfrage erst Sinn, wenn das System im regulären Betrieb läuft und nicht zum Beispiel beim Boot.
Type=oneshot: Dieser Typ ist ideal für Aufgaben, die eine kurze, definierte Aktion ausführen und sich danach wieder beenden. Im Gegensatz zu dauerhaften Hintergrunddiensten wartet systemd bei oneshot exakt so lange, bis das Skript komplett durchgelaufen ist, und wechselt erst dann den Status der Unit auf “beendet”.
ExecStart=/usr/local/sbin/scheduled-reboot : Hier wird der absolute Pfad zu dem Befehl oder Skript definiert, das beim Aktivieren der Unit ausgeführt werden soll. Wichtig: systemd sucht nicht in den Standard-Umgebungsvariablen ($PATH) nach Befehlen. Daher ist die Angabe des vollständigen, absoluten Pfads (/usr/local/sbin/... statt nur scheduled-reboot) hier zwingend erforderlich.
Das Skript
Damit der Server um 03:30 Uhr nicht blind neu startet, prüft das Skript /usr/local/sbin/scheduled-reboot vorab zwei wichtige Kriterien:
- Steht überhaupt ein Update-Reboot an? (Prüfung via Vorhandensein von
/run/reboot-required) - Arbeitet aktuell noch jemand auf dem Server?
#!/bin/bash
# Schritt 1: Wir Prüfen, ob ein Reboot gefordert ist
if [ ! -e /run/reboot-required ]; then
exit 0
fi
# Schritt 2: Wir Prüfen, ob aktive SSH- oder Terminal-Sitzungen existieren
if loginctl list-users --no-legend | grep -q .; then
logger -t scheduled-reboot "Reboot übersprungen: Aktive Benutzersitzung gefunden."
exit 0
fi
# Schritt 3: Alle Bedingungen erfüllt – Neustart einleiten
logger -t scheduled-reboot "Reboot-Notwendig + keine User angemeldet. System wird neu gestartet."
exec systemctl reboot
Timer aktivieren und überwachen
Nachdem die Dateien angelegt wurden, liest systemd die Konfiguration neu ein und startet den Timer:
sudo systemctl daemon-reload
sudo systemctl enable --now scheduled-reboot.timer
Wenn wir den Befehl systemctl enable ausführen, knüpft systemd durch den [Install]-Block mit WantedBy=timers.target einen symbolischen Link (Symlink) im Verzeichnis /etc/systemd/system/timers.target.wants/. Dadurch wird der Timer jetzt fest im System verankert und beim Erreichen des entsprechenden Boot-Status jedes mal automatisch mitgeladen.
Der große Komfortgewinn gegenüber Cron zeigt sich bei der Wartung. Wir können den Status des Timers und die nächste geplante Ausführung jederzeit transparent einsehen:
systemctl list-timers scheduled-reboot.timer
Falls wir wissen möchten, warum ein Reboot in der Nacht nicht stattgefunden hat, liefert das Journal die exakte Antwort, da jede Ausführung des Services sauber protokolliert wird:
journalctl -u scheduled-reboot.service
Warum nicht einfach Cron?
Ein klassischer Cron-Eintrag wäre unbestreitbar kürzer:
30 3 * * * /usr/local/sbin/scheduled-reboot
Für Kleinstaufgaben spricht auch weiterhin absolut nichts gegen Cron. Wer jedoch systemd.timer nutzt profitiert von mächtigen und zeitgemäßen Bordmitteln, die Cron standardmäßig fehlen. Einige Beispiele:
Bordmittel: RandomizedDelaySec=: Verhindert, dass zum Beispiel mehrere Container oder VMs exakt zur selben Sekunde die Festplatten des Hypervisors mit einem Reboot stressen.
Zentrales Logging: Keine lokalen System-Mails, sondern nahtlose Integration in das journald-Logsystem.
Abhängigkeiten: Ein Timer kann explizit darauf warten, bis das Netzwerk oder ein bestimmter Mount-Point oder ein anderer Service etc. bereit ist.