Home » Linux » systemd-Timer statt Cronjob: einrichten und überwachen

systemd-Timer statt Cronjob: einrichten und überwachen

Ein Cronjob, der nicht mehr läuft, meldet sich nicht. Er verschwindet einfach aus eurem Leben, und ihr merkt es an dem Tag, an dem ihr die Sicherung braucht. Genau dieses Loch schliessen systemd-Timer: Sie bringen die geplante Aufgabe in dieselbe Verwaltung wie jeden anderen Dienst, mit Statusabfrage, Logbuch und – der eigentliche Gewinn – einem Alarm, wenn etwas schiefgeht. Diese Anleitung baut einen solchen Timer von Grund auf, erklärt die Zeitsyntax und hängt am Ende eine Push-Benachrichtigung dran.

Dunkles Titelbild mit dem Schriftzug „systemd-Timer statt Cronjob" und einem Terminalfenster darunter. Darin steht der Befehl systemctl list-timers backup-home.timer und seine Ausgabe mit den Spalten NEXT, LEFT, UNIT und ACTIVATES: Do 2026-09-03 02:14, 19h left, backup-home.timer, backup-home.service. Das Bild zeigt, dass ein systemd-Timer den nächsten Ausführungszeitpunkt und den zugehörigen Dienst jederzeit abfragbar macht.

Warum systemd-Timer den Cronjob ablösen

Cron macht genau eine Sache: Es startet zur eingetragenen Zeit einen Befehl. Was danach passiert, interessiert es nicht. Läuft der Befehl auf einen Fehler, landet die Ausgabe im besten Fall in einer lokalen Mail, die niemand liest, und im schlechteren Fall in /dev/null, weil das jemand vor drei Jahren so eingetragen hat, damit das Log nicht zuläuft. Wie diese Umleitung funktioniert, haben wir in stdout, stderr und /dev/null auseinandergenommen – bei Cron ist sie der häufigste Grund, warum ein Fehlschlag unbemerkt bleibt.

Falls ihr Cron bisher klassisch benutzt habt, etwa für die Hintergrundaufgaben einer Nextcloud, kennt ihr die Bedienung aus unserer Anleitung Cronjob für Eure Nextcloud erstellen. Alles, was dort steht, funktioniert weiterhin. Es gibt nur eben inzwischen einen zweiten Weg.

Amazon ist dabei am deutlichsten geworden. In der Dokumentation zu Amazon Linux 2023 steht schwarz auf weiss: „In AL2023, cronie is not included by default. Therefore, support for crontab is no longer provided by default." Und weiter: „We recommend that you migrate to systemd timers due to the added functionality provided by systemd." Nachzulesen in der AL2023-Dokumentation zu Cron. Auf Debian, Ubuntu und Mint ist Cron weiterhin installiert – aber ein wachsender Teil der Systemwartung läuft dort ohnehin schon über Timer-Units, ohne dass es jemand gemerkt hätte.

Der praktische Unterschied lässt sich in eine Tabelle packen:

PunktCronsystemd-Timer
Ausgabe und FehlerMail oder nirgendwoimmer im Journal, abfragbar mit journalctl -u
Status abfragennicht vorgesehensystemctl status, systemctl list-timers
Verpasster Terminfällt aus, ausser mit anacronPersistent=true holt ihn nach
Alle Jobs gleichzeitigLastspitze zur vollen StundeRandomizedDelaySec= verteilt sie
Fehler meldenselbst bauenOnFailure= startet eine beliebige Unit
Rechte, Limits, Isolationüber das Skriptüber die Unit selbst
Syntax prüfenRätselratensystemd-analyze calendar
Aufwandeine Zeilezwei Dateien

Die letzte Zeile ist der ehrliche Teil. Wer alle fünf Minuten ein Einzeiler-Skript anstossen will und sonst nichts, ist mit Cron schneller fertig. Für alles, dessen Ausfall wehtut – Sicherungen, Zertifikatserneuerungen, Datenbankpflege – lohnt sich der Mehraufwand ab dem ersten stillen Fehlschlag.

Voraussetzungen und eine Vorwarnung

Ihr braucht ein System mit systemd. Das trifft auf Debian, Ubuntu, Linux Mint, Fedora, openSUSE und Arch zu, also auf praktisch alles, was auf dieser Seite sonst behandelt wird. Prüfen lässt sich das mit:

systemctl --version

Für Units im Systemkontext braucht ihr Root-Rechte. Alle folgenden Befehle mit sudo setzen voraus, dass euer Benutzer das darf. Wer sich bei Benutzern, Gruppen und Rechten unsicher ist, findet die Grundlagen in Linux Gruppen und Berechtigungen einfach erklärt.

Wichtig: Legt eigene Units immer unter /etc/systemd/system/ ab, niemals unter /lib/systemd/system/ oder /usr/lib/systemd/system/. Das zweite Verzeichnis gehört der Paketverwaltung, und das nächste Update überschreibt eure Datei kommentarlos.

Die zwei Dateien: eine Service-Unit und eine Timer-Unit

Ein systemd-Timer führt selbst nichts aus. Er ist ausschliesslich der Wecker; die Arbeit macht eine Service-Unit. Die Handbuchseite systemd.timer(5) beschreibt die Zuordnung so: Ein Timer namens foo.timer aktiviert standardmässig die Unit foo.service, also den gleichen Namen mit anderer Endung. Solange ihr beide Dateien gleich benennt, müsst ihr die Verbindung nirgends eintragen.

Als durchgehendes Beispiel dient eine nächtliche Sicherung des Home-Verzeichnisses. Das eigentliche Skript liege unter /usr/local/bin/backup-home.sh und sei ausführbar.

Schritt 1: Die Service-Unit anlegen

sudo nano /etc/systemd/system/backup-home.service

Inhalt:

[Unit]
Description=Naechtliche Sicherung des Home-Verzeichnisses
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-home.sh
User=mario
Nice=10
IOSchedulingClass=idle

Drei Zeilen verdienen eine Erklärung. Type=oneshot ist der Typ für Aufgaben, die anlaufen, ihre Arbeit tun und sich beenden. Laut systemd.service(5) betrachtet der Dienstmanager die Unit erst dann als gestartet, wenn der Hauptprozess sich beendet hat – „the service manager will consider the unit up after the main process exits". Genau das wollt ihr hier, denn eine Sicherung ist fertig oder nicht.

User=mario lässt das Skript unter eurem Benutzer laufen statt als Root – ersetzt den Namen, und lasst die Zeile weg, falls das Skript Root braucht. Nice=10 und IOSchedulingClass=idle drängen den Job hinter interaktive Arbeit zurück; die Sicherung darf zehn Minuten länger dauern, wenn dafür niemand ruckelt.

Es fehlt eine Zeile, die ihr aus Cron kennt: die Umleitung der Ausgabe. Die braucht es nicht. Alles, was das Skript auf stdout und stderr schreibt, landet ohne Zutun im Journal – so beschreibt es systemd.exec(5) im Abschnitt zu Logging und Standard-Ein- und -Ausgabe.

Schritt 2: Die Timer-Unit anlegen

sudo nano /etc/systemd/system/backup-home.timer

Inhalt:

[Unit]
Description=Startet die naechtliche Home-Sicherung

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=900
AccuracySec=1min

[Install]
WantedBy=timers.target

Der Abschnitt [Install] mit WantedBy=timers.target ist der Grund, warum der Timer nach einem Neustart wieder da ist. Fehlt er, lässt sich die Unit zwar starten, aber nicht dauerhaft aktivieren.

Schritt 3: Einlesen und aktivieren

sudo systemctl daemon-reload
sudo systemctl enable --now backup-home.timer

daemon-reload lädt die Konfiguration des Dienstmanagers neu; laut systemctl(1) werden dabei alle Unit-Dateien neu eingelesen und der gesamte Abhängigkeitsbaum neu aufgebaut. Ohne diesen Schritt kennt systemd eure Datei nicht. enable --now aktiviert die Unit und startet sie in einem Aufwasch.

Beachtet: Aktiviert wird der Timer, nicht der Service. Der Service bleibt inaktiv und wird vom Timer geweckt. Ein häufiger Anfängerfehler ist systemctl enable backup-home.service – damit läuft die Sicherung bei jedem Systemstart genau einmal und danach nie wieder.

Testen lässt sich der Ablauf jederzeit, ohne auf zwei Uhr nachts zu warten:

sudo systemctl start backup-home.service
systemctl status backup-home.service

Damit ist die Grundkonstruktion fertig. Wie die drei beteiligten Units zusammenspielen – die dritte kommt weiter unten dazu – zeigt das folgende Schaubild.

Schaubild mit drei Karten nebeneinander. Links backup-home.timer als Wecker mit den Einstellungen OnCalendar, Persistent und RandomizedDelaySec; ein Pfeil mit der Beschriftung „startet die Unit mit gleichem Namen" führt zur mittleren Karte backup-home.service mit Type=oneshot und ExecStart, deren Ausgabe ins Journal geht; ein rot beschrifteter Pfeil „nur im Zustand failed, über OnFailure=" führt zur rechten Karte alarm@.service, die eine Push-Nachricht verschickt. Die Grafik zeigt, dass nur der Timer aktiviert wird, der Dienst von ihm gestartet wird und die dritte Unit ausschliesslich bei einem Fehlschlag läuft.

OnCalendar: die Zeitsyntax, die kein Cron-Format ist

Hier liegt die grösste Umgewöhnung. OnCalendar= benutzt nicht die fünf Sternchen aus der Crontab, sondern eine eigene Schreibweise. Die Handbuchseite systemd.time(7) beschreibt das Format als Wochentag Jahr-Monat-Tag Stunde:Minute:Sekunde. Der Wochentag darf fehlen, das Datum ebenso; die Uhrzeit ist der Kern.

Die Operatoren sind schnell erzählt. Ein Sternchen passt auf jeden Wert. Mehrere Werte werden mit Komma aufgezählt. Zwei Punkte bilden einen Bereich, etwa Mon..Fri. Der Schrägstrich bedeutet Wiederholung, also *:0/15 für alle fünfzehn Minuten.

Zusätzlich gibt es Kurzformen, die die Handbuchseite ausdrücklich als Abkürzungen ausweist:

Kurzformsteht für
minutely*-*-* *:*:00
hourly*-*-* *:00:00
daily*-*-* 00:00:00
weeklyMon *-*-* 00:00:00
monthly*-*-01 00:00:00
quarterly*-01,04,07,10-01 00:00:00
semiannually*-01,07-01 00:00:00
yearly*-01-01 00:00:00

Ein paar praktische Ausdrücke, die die meisten Fälle abdecken:

OnCalendar=*-*-* 02:00:00          # jede Nacht um 02:00
OnCalendar=Mon..Fri *-*-* 07:30    # werktags um 07:30
OnCalendar=Sun *-*-* 04:00:00      # sonntags um 04:00
OnCalendar=*-*-01 03:00:00         # am Ersten jedes Monats um 03:00
OnCalendar=*:0/15                  # alle 15 Minuten
OnCalendar=hourly                  # zur vollen Stunde

Mehrere OnCalendar=-Zeilen in derselben Unit sind erlaubt und ergeben mehrere Auslösezeitpunkte. Eine leere Zuweisung OnCalendar= löscht alle bisherigen Angaben – nützlich in Drop-in-Dateien, verwirrend, wenn es unbeabsichtigt passiert.

Den Ausdruck prüfen, bevor ihr ihn einsetzt

Ihr müsst nicht raten. systemd-analyze hat einen Unterbefehl, der genau dafür da ist. Die Handbuchseite systemd-analyze(1) beschreibt ihn so: „This command will parse and normalize repetitive calendar time events, and will calculate when they elapse next."

systemd-analyze calendar "Mon..Fri *-*-* 07:30"

Die Ausgabe nennt die ursprüngliche Form, die normalisierte Form, den nächsten Auslösezeitpunkt und den Abstand bis dahin. Steht dort ein Datum, das euch überrascht, habt ihr euch vertan – und das vor dem Produktivbetrieb statt danach. Verwandt und ebenso nützlich ist systemd-analyze verify /etc/systemd/system/backup-home.service: Der Befehl meldet unbekannte Abschnitte, fehlende Abhängigkeiten und nicht ausführbare Befehle.

Kalenderausdrücke werden in der lokalen Zeitzone des Systems ausgewertet. Auf einem Server, der auf UTC steht, ist „02:00" also nicht eure Bettzeit – ein Blick auf timedatectl vor dem ersten Timer erspart Verwunderung.

Persistent, RandomizedDelaySec und der Rest der Feineinstellung

Die vier Zeilen aus dem Beispiel sind der Punkt, an dem ein Timer den Cronjob inhaltlich überholt. Alle stammen aus dem Abschnitt [Timer] und sind in systemd.timer(5) beschrieben.

Persistent=true speichert den letzten Auslösezeitpunkt auf der Platte. War das System zum geplanten Termin aus, wird der Job unmittelbar nach dem Hochfahren nachgeholt. Das ist die Funktion, für die es unter Cron ein zweites Programm braucht – anacron –, und auf einem Laptop, der nachts um zwei zugeklappt im Rucksack liegt, ist sie der Unterschied zwischen einer Sicherung und einer Sicherungsabsicht.

RandomizedDelaySec=900 verzögert die Ausführung um eine zufällig gewählte, gleichmässig verteilte Zeitspanne zwischen null und dem angegebenen Wert. Wenn zwanzig Maschinen um Punkt zwei Uhr dieselbe Netzwerkfreigabe ansteuern, verteilt das die Last über eine Viertelstunde. Wollt ihr die Verzögerung stabil statt bei jedem Start neu ausgewürfelt, setzt zusätzlich FixedRandomDelay=true: Der Versatz wird dann aus Maschinen-ID und Unit-Namen abgeleitet und bleibt gleich. Wie stark eure Maschine unter solchen Spitzen tatsächlich leidet, seht ihr mit den Werkzeugen aus Nützliche Linux Konsolen Tools für das Systemmonitoring.

AccuracySec= gibt an, wie genau der Timer treffen soll, und steht laut Handbuch standardmässig auf einer Minute. systemd nutzt diese Toleranz, um Aufwachvorgänge zu bündeln und den Rechner länger schlafen zu lassen. Wer es exakt braucht, setzt AccuracySec=1us – und zahlt das mit Stromverbrauch.

Drei weitere Optionen sind gut zu kennen. OnBootSec= definiert einen Zeitpunkt relativ zum Systemstart, OnUnitActiveSec= einen relativ zur letzten Aktivierung der Zieleinheit. Kombiniert ergibt das „fünf Minuten nach dem Start und danach alle sechs Stunden", ohne jeden Kalender:

[Timer]
OnBootSec=5min
OnUnitActiveSec=6h

Und WakeSystem=true weckt das System aus dem Bereitschaftsbetrieb, wenn der Timer fällig wird. Auf einem Server irrelevant, auf einem Heim-NAS mit Ruhezustand entscheidend.

Soll der Timer eine Unit mit abweichendem Namen starten, gebt sie ausdrücklich an – Unit= ist laut Handbuch „the unit to activate when this timer elapses". Nötig ist das nur, wenn ihr von der Namenskonvention abweicht.

Nachsehen, ob es läuft: list-timers und das Journal

Der wichtigste Befehl im Alltag ist eine Übersicht aller Timer, sortiert nach dem nächsten Auslösezeitpunkt:

systemctl list-timers backup-home.timer

Die Ausgabe sieht so aus:

NEXT                 LEFT      UNIT               ACTIVATES
Do 2026-09-03 02:14  19h left  backup-home.timer  backup-home.service

Die Spalten sind in systemctl(1) beschrieben: NEXT zeigt, wann der Timer das nächste Mal läuft, LEFT wie lange es bis dahin dauert, LAST und PASSED dasselbe rückwärts, UNIT den Namen des Timers und ACTIVATES den Dienst, den er startet. Ohne Argument listet der Befehl alle Timer des Systems auf – der schnellste Weg zu sehen, was auf einer fremden Maschine eigentlich alles nachts passiert. systemctl list-timers --all zeigt zusätzlich die inaktiven.

Die 02:14 in der Ausgabe sind übrigens kein Tippfehler: Das ist RandomizedDelaySec bei der Arbeit.

Was der Job beim letzten Lauf gesagt hat, steht im Journal. Die Option -u filtert laut journalctl(1) auf die Meldungen einer bestimmten Unit:

journalctl -u backup-home.service -n 50 --no-pager
journalctl -u backup-home.service --since "-7d" -p warning
journalctl -u backup-home.service -f

Die erste Zeile zeigt die letzten fünfzig Meldungen, die zweite alle Warnungen und Schlimmeres der vergangenen sieben Tage, die dritte hängt sich live an die Ausgabe und läuft bis Strg+C. Wer tiefer in die Fehlersuche einsteigen will, findet den Rest in unserer Anleitung Linux Problembehebung im systemd.

Nützlich ist ausserdem systemctl cat backup-home.timer: Der Befehl zeigt die Unit so, wie systemd sie tatsächlich sieht, samt allen Drop-ins – und beendet jede Diskussion darüber, welche der drei Kopien nun gilt.

Der eigentliche Gewinn: OnFailure meldet den Fehlschlag

Bis hierhin habt ihr einen Cronjob mit mehr Schreibarbeit. Der Unterschied beginnt jetzt. In systemd.unit(5) steht eine Option, die es unter Cron schlicht nicht gibt: OnFailure= ist „a space-separated list of one or more units that are activated when this unit enters the 'failed' state." Anders gesagt: Geht die Sicherung schief, startet systemd von sich aus eine zweite Unit eurer Wahl. Was diese Unit tut, ist euch überlassen – eine Push-Nachricht, eine Mail, ein Eintrag in ein Monitoring.

Eine Benachrichtigungs-Unit als Vorlage anlegen

Damit ihr die Unit nicht für jeden Job neu schreibt, legt ihr sie als Vorlage an. Der Klammeraffe im Dateinamen macht daraus eine Template-Unit, deren Instanzname später den Namen des fehlgeschlagenen Dienstes trägt.

sudo nano /etc/systemd/system/alarm@.service
[Unit]
Description=Meldet den Fehlschlag von %i per ntfy

[Service]
Type=oneshot
ExecStart=/usr/bin/curl -sS \
  -H "Title: Fehlgeschlagen: %i" \
  -H "Priority: 5" \
  -H "Tags: warning" \
  -d "Auf diesem System ist %i fehlgeschlagen. Details mit: journalctl -u %i -n 50" \
  https://ntfy.sh/EUER-LANGES-ZUFALLSTHEMA

Der Platzhalter %i ist in systemd.unit(5) als Instance name definiert: die Zeichenkette zwischen dem ersten @ und der Endung. Ihr braucht die Unit also genau einmal, egal wie viele Jobs sie später melden soll.

Prüft, ob curl überhaupt installiert ist, und ob der Pfad stimmt:

which curl

Liegt es woanders, tragt den tatsächlichen Pfad ein. ExecStart= verlangt einen absoluten Pfad und findet nichts über die PATH-Variable.

Die Vorlage im Job eintragen

Ergänzt den Abschnitt [Unit] eurer Service-Unit um eine Zeile:

[Unit]
Description=Naechtliche Sicherung des Home-Verzeichnisses
Wants=network-online.target
After=network-online.target
OnFailure=alarm@%n.service

%n ist laut Handbuch der full unit name, hier also backup-home.service. Aus alarm@%n.service wird damit die Unit alarm@backup-home.service.service, und in der Benachrichtigung steht %i für backup-home.service. Das sieht auf den ersten Blick nach einem Tippfehler aus und ist genau richtig so.

sudo systemctl daemon-reload

ntfy einrichten und den Alarm testen

ntfy verschickt Push-Nachrichten über einen einfachen HTTP-Aufruf. Die Projektdokumentation zeigt den Aufbau: Der Nachrichtentext geht per -d an eine URL, deren letzter Bestandteil das Thema ist; Titel, Priorität und Schlagwörter werden über die Kopfzeilen Title, Priority und Tags gesetzt. Priorität 5 ist die höchste von fünf Stufen.

Sicherheitshinweis, und der ist ernst gemeint: Bei ntfy gibt es keine Anmeldung für das blosse Senden und Empfangen. Die Projektdokumentation formuliert das unmissverständlich: „the topic is essentially a password", ihr sollt also etwas wählen, das nicht zu erraten ist. Wer backup als Thema nimmt, teilt seine Ausfallmeldungen mit der halben Welt. Nehmt eine lange Zufallszeichenkette, zum Beispiel erzeugt mit head -c 18 /dev/urandom | base64 | tr -d '/+='. Wer den Dienst nicht in fremden Händen haben will, betreibt ntfy selbst – der Server ist quelloffen und in einem Container schnell aufgesetzt.

Installiert die ntfy-App auf dem Smartphone, abonniert dort euer Thema und löst dann absichtlich einen Fehler aus. Am einfachsten geht das mit einer Wegwerf-Unit:

sudo systemd-run --unit=alarmtest --property=OnFailure=alarm@alarmtest.service /bin/false

/bin/false beendet sich mit einem Fehlerstatus, die Unit geht damit in den Zustand failed, und das Telefon sollte klingeln. Kommt nichts an, prüft zuerst die Benachrichtigungs-Unit selbst:

journalctl -u "alarm@*" -n 30 --no-pager

Wollt ihr statt einer Push-Nachricht eine Mail, ersetzt den curl-Aufruf durch ein Skript, das systemctl status %i und journalctl -u %i -n 50 einsammelt und an sendmail oder msmtp übergibt. Der Mechanismus bleibt derselbe; systemd interessiert sich nicht dafür, was die Unit tut, sondern nur dafür, dass sie startet. Für Erfolgsmeldungen gibt es das Gegenstück OnSuccess=, ausgelöst beim Wechsel in den Zustand inactive.

Timer ohne Root: der Benutzerkontext

Nicht jede Aufgabe gehört ins Systemverzeichnis. Alles, was nur euer Benutzer betrifft – Mails abholen, ein Verzeichnis synchronisieren, eine Datensicherung eures Home – lässt sich im Benutzerkontext ablegen. Die Dateien liegen dann unter ~/.config/systemd/user/, und sämtliche Befehle bekommen --user und kein sudo:

mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/backup-home.service
nano ~/.config/systemd/user/backup-home.timer
systemctl --user daemon-reload
systemctl --user enable --now backup-home.timer
systemctl --user list-timers

Zwei Anpassungen sind nötig. Erstens entfällt die Zeile User=, denn der Dienst läuft ohnehin unter euch. Zweitens lautet der Abschnitt [Install] hier WantedBy=default.target statt timers.target.

Und dann kommt die Falle, über die alle einmal stolpern: Benutzer-Units werden standardmässig beendet, sobald ihr euch abmeldet. Auf einem Server, an dem ihr euch nur zum Arbeiten anmeldet, läuft der Timer damit praktisch nie. Abhilfe schafft „Lingering", beschrieben in loginctl(1): „If enabled for a specific user, a user manager is spawned for the user at boot and kept around after logouts."

sudo loginctl enable-linger mario
loginctl user-status mario

Ersetzt den Benutzernamen. Ab jetzt startet der Benutzermanager beim Systemstart und bleibt nach dem Abmelden bestehen. Rückgängig macht das sudo loginctl disable-linger mario.

Das Journal des Benutzers lest ihr mit journalctl --user -u backup-home.service. Ohne --user sucht ihr an der falschen Stelle und findet nichts – ein Fehler, der schon manchen an seinem Verstand hat zweifeln lassen.

Ein bestehendes Backup umstellen

Der häufigste Anwendungsfall ist die Sicherung. Wer nach unserer Anleitung Linux PC lokal und auf NAS sichern mit Borg arbeitet, hat den Zeitplan bisher in der Oberfläche von Vorta hängen – was voraussetzt, dass die grafische Sitzung läuft. Ein Timer löst dieselbe Aufgabe unabhängig von Anmeldung und Desktop.

Das Vorgehen in vier Schritten: den Ablauf in ein Skript unter /usr/local/bin/ giessen und von Hand ausführen, bis er sauber durchläuft; Service- und Timer-Unit wie oben anlegen; OnFailure= eintragen und den Alarm testen; zuletzt den alten Cron-Eintrag entfernen – crontab -e für den Benutzer, sudo crontab -e für Root, dazu ein Blick in /etc/cron.d/. Wer den alten Eintrag stehen lässt, hat zwei Sicherungen, die sich um dasselbe Verzeichnis streiten.

Ein Wort zum Rückgabewert: systemd erkennt einen Fehlschlag daran, dass der Prozess mit einem Status ungleich null endet. Ein Shell-Skript, das brav exit 0 ans Ende schreibt oder dessen Fehler in einer Pipe verschwinden, meldet niemals einen Fehler – und dann nützt das schönste OnFailure= nichts. Setzt an den Anfang eures Skripts set -euo pipefail, dann bricht es bei einem Fehler ab und gibt ihn weiter.

Wenn etwas nicht klappt

Der Timer taucht in list-timers nicht auf. Entweder fehlt daemon-reload, oder der Timer ist nur gestartet und nicht aktiviert, oder im Abschnitt [Install] fehlt WantedBy=. Prüft mit systemctl is-enabled backup-home.timer.

Der Timer läuft, aber nichts passiert. Wahrscheinlich stimmt die Namenspaarung nicht. Der Timer backup-home.timer sucht backup-home.service; heisst eure Service-Datei anders, tragt Unit= im Abschnitt [Timer] ein. Die Spalte ACTIVATES in list-timers verrät, was er tatsächlich zu starten versucht.

Die Unit geht sofort in failed, Status 203/EXEC. Der Pfad in ExecStart= stimmt nicht oder die Datei ist nicht ausführbar. Absoluter Pfad ist Pflicht, und chmod +x hilft.

Status 217/USER oder 226/NAMESPACE. Der in User= angegebene Benutzer existiert nicht, oder ihm fehlt der Zugriff auf ein Verzeichnis. Prüft den Namen und die Rechte des Zielpfads.

Das Skript läuft von Hand, aber nicht als Unit. Der Klassiker. Units erben eure Shell-Umgebung nicht: kein PATH wie im Terminal, keine Variablen aus .bashrc, kein SSH-Agent, kein DISPLAY. Schreibt absolute Pfade und setzt benötigte Variablen mit Environment= oder EnvironmentFile= in der Unit.

Der Job läuft nachts nicht, weil der Rechner aus war. Dafür ist Persistent=true da. Bei einem Gerät im Ruhezustand hilft zusätzlich WakeSystem=true.

Der Benutzer-Timer läuft nur, solange ihr angemeldet seid. sudo loginctl enable-linger BENUTZER, siehe oben.

Der Alarm kommt nicht an. Prüft mit journalctl -u "alarm@*", ob die Benachrichtigungs-Unit überhaupt gestartet ist. Wurde sie gestartet und curl meldet einen Fehler, fehlt meist die Netzwerkverbindung zum Zeitpunkt des Fehlschlags oder das Thema ist falsch geschrieben. Steht in der Ausgabe gar nichts, wurde OnFailure= nicht ausgelöst – dann endet euer Skript vermutlich mit Status null, obwohl es gescheitert ist.

Fazit: Ein Job, der schweigt, ist kein Job

Der Mehraufwand gegenüber einer Cron-Zeile ist real und überschaubar: zwei Dateien statt einer Zeile, eine ungewohnte Zeitsyntax, ein Befehl zum Neuladen. Dafür bekommt ihr eine geplante Aufgabe, die sich wie jeder andere Dienst abfragen lässt, deren Ausgabe automatisch im Journal steht, die einen verpassten Termin nachholt und die euch anruft, wenn sie gescheitert ist.

Der letzte Punkt ist der einzige, auf den es wirklich ankommt. Eine Sicherung, von der ihr nicht wisst, ob sie läuft, ist keine Sicherung, sondern eine Hoffnung mit Speicherplatzbedarf. Cron kann euch das nicht sagen, weil es dafür nie gebaut wurde. Ein Timer mit OnFailure= kann es, und der Aufwand dafür beträgt sechs Zeilen.

Fangt mit dem einen Job an, dessen Ausfall euch stören würde, und stellt genau den um. Der Rest darf in Ruhe folgen – oder bei Cron bleiben. Es ist keine Glaubensfrage, sondern eine Frage danach, was passieren soll, wenn etwas schiefgeht.

Quellen und weiterführende Links

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert