Tutorials  /  Linux Basics

Systemd-Timer statt Cronjobs einrichten

LLudwig · August 2026 ·10 Min. Lesezeit ·Linux Basics, Tutorial

Cronjobs liefern dir keinen Exit-Status, kein strukturiertes Log und keine Möglichkeit, einen Lauf nachzuholen, der während eines Reboots ausgefallen ist. systemd-Timer lösen genau diese Punkte: Jeder Lauf ist eine eigene Unit mit Statuscode, Journal-Eintrag und optionalen Ressourcenlimits. Diese Anleitung zeigt dir am Beispiel eines nächtlichen Datenbank-Dumps, wie du einen bestehenden Cronjob auf einen systemd-Timer umstellst und das Ergebnis verifizierst.

Was ist ein systemd-Timer?

Ein systemd-Timer ist eine Unit-Datei mit der Endung .timer, die eine gleichnamige .service-Unit zu festgelegten Zeitpunkten startet und jeden Lauf mit Exit-Status, Dauer und Ausgabe im systemd-Journal protokolliert.

Ein Timer besteht immer aus zwei Dateien: der .service-Unit mit der eigentlichen Arbeit und der .timer-Unit mit dem Zeitplan. systemd verknüpft beide automatisch über den identischen Basisnamen, backup-db.timer startet also backup-db.service. Nur wenn du einen abweichenden Namen brauchst, setzt du im Timer explizit Unit=.

systemd kennt zwei Timer-Typen. Kalender-Timer (OnCalendar=) feuern zu Wanduhrzeiten und ersetzen den klassischen Crontab-Eintrag. Monotone Timer (OnBootSec=, OnUnitActiveSec=) feuern relativ zum Systemstart oder zum letzten Lauf. Für „15 Minuten nach dem Boot" oder „alle 6 Stunden ab dem letzten erfolgreichen Lauf" gibt es in Cron kein Äquivalent.

Warum systemd-Timer statt Cronjobs?

Systemd-Timer bieten gegenüber Cronjobs vier konkrete Vorteile: durchsuchbare Logs im Journal, das Nachholen verpasster Läufe über Persistent=true, echte Abhängigkeiten zu anderen Units und Ressourcenlimits wie MemoryMax pro Job.

Eigenschaft Cron systemd-Timer
Ausgabe Mail an lokalen User journalctl -u <unit>
Exit-Status sichtbar Nein systemctl status <unit>
Verpasster Lauf nachholbar Nein (nur anacron) Persistent=true
Abhängigkeit zu Netzwerk/Mount Nein After=, Requires=
Ressourcenlimits Nein MemoryMax=, CPUQuota=
Nächster Lauf abfragbar Nein systemctl list-timers
Überlappende Läufe Möglich Verhindert (Service läuft bereits)

Der letzte Punkt ist in der Praxis der wichtigste. Ein Cronjob, der länger läuft als sein Intervall, startet fröhlich ein zweites Mal. systemd startet einen bereits laufenden Service nicht erneut und schreibt stattdessen einen Hinweis ins Journal.

Voraussetzungen

  • Ein Linux-Host mit systemd ab Version 230 (Debian 10+, Ubuntu 18.04+, Rocky/AlmaLinux 8+). Prüfe mit systemctl --version.
  • Ein Benutzerkonto mit sudo-Rechten
  • Ein ausführbares Skript oder Kommando, das bisher per Cron lief

Zum Nachbauen reicht eine skalierbare Cloud-VM mit einer aktuellen Linux-Distribution. Alle Beispiele nutzen den Pfad /usr/local/bin/backup-db.sh als Platzhalter für dein eigenes Skript, ersetze ihn entsprechend.

Konsole
$ systemctl --version | head -n 1
systemd 252 (252.22-1~deb12u1)

Ablauf eines Timer-Laufs

Bevor du die Units schreibst, hilft ein Blick auf die Kette vom Boot bis zum Journal-Eintrag. Besonders die Verzweigung bei einem verpassten Zeitpunkt entscheidet darüber, ob ein nächtlicher Job nach einem Reboot am Morgen noch läuft.

graph TD
    A["timers.target beim Boot"] --> B["backup-db.timer aktiv"]
    B --> C{"OnCalendar-Zeitpunkt erreicht?"}
    C -->|"ja, System läuft"| E["backup-db.service starten"]
    C -->|"nein, System war aus"| F{"Persistent=true gesetzt?"}
    F -->|ja| E
    F -->|nein| G["Lauf wird übersprungen"]
    E --> H["ExecStart ruft /usr/local/bin/backup-db.sh auf"]
    H --> I{"Exit-Code 0?"}
    I -->|ja| J["Status inactive (dead), Erfolg im Journal"]
    I -->|nein| K["Status failed, Fehler im Journal"]
VM

Passende Infrastruktur bei centron

Zum Mitmachen braucht es keine eigene Hardware: ccloud³ VMs mit vollem Root-Zugriff, stundengenau abgerechnet und in Sekunden startklar. Cloud-Server mieten →

Service-Unit anlegen

Lege zuerst die Unit an, die die eigentliche Arbeit erledigt. Type=oneshot sagt systemd, dass der Prozess sich beendet und das kein Fehler ist.

ini
[Unit]
Description=Datenbank-Dump nach /var/backups
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/bin/backup-db.sh
MemoryMax=512M
Nice=10

Speichere die Datei als /etc/systemd/system/backup-db.service. Die wichtigsten Direktiven im Detail:

  • Type=oneshot: systemd betrachtet den Service als erfolgreich, wenn der Prozess mit Exit-Code 0 endet. Ohne diese Angabe (Type=simple) gilt ein sich beendender Prozess als Fehlschlag.
  • User= / Group=: Der Job läuft nicht als root. Lege das Konto vorher an oder setze einen bestehenden Systembenutzer ein.
  • After=network-online.target: Nötig, wenn das Skript eine Remote-Datenbank oder ein NFS-Ziel erreicht. Bei rein lokalen Jobs kannst du beide network-Zeilen weglassen.
  • MemoryMax=: Harte Speichergrenze über cgroups. Der Job wird beendet statt den Host in den OOM-Killer zu treiben.

Eine [Install]-Sektion braucht die Service-Unit nicht. Sie wird nie direkt aktiviert, sondern ausschließlich vom Timer gestartet.

Timer-Unit anlegen

Die Timer-Unit trägt denselben Basisnamen und enthält nur den Zeitplan. Speichere sie als /etc/systemd/system/backup-db.timer.

ini
[Unit]
Description=Startet backup-db.service taeglich um 02:30 Uhr

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

Drei Direktiven bestimmen das Verhalten:

  • OnCalendar=: Der Zeitplan im Format Wochentag Jahr-Monat-Tag Stunde:Minute:Sekunde. Ein * steht für „jeder Wert".
  • Persistent=true: systemd speichert den Zeitpunkt des letzten Laufs unter /var/lib/systemd/timers/. War der Host zum geplanten Zeitpunkt aus, startet der Service unmittelbar nach dem nächsten Boot nach.
  • RandomizedDelaySec=300: Verzögert den Start um einen zufälligen Wert zwischen 0 und 300 Sekunden. Auf mehreren Hosts mit identischem Zeitplan verhinderst du damit, dass alle gleichzeitig auf dasselbe Backup-Ziel schreiben.

Die Genauigkeit steuert AccuracySec=, der Standardwert liegt bei 60 Sekunden. systemd darf den Start innerhalb dieses Fensters verschieben, um Wakeups zu bündeln. Setze AccuracySec=1s nur, wenn der exakte Zeitpunkt wirklich zählt.

OnCalendar-Ausdrücke aus Cron übersetzen

Crontab OnCalendar
30 2 * * * *-*-* 02:30:00
0 * * * * hourly
*/15 * * * * *:0/15
0 4 * * 1 Mon *-*-* 04:00:00
0 0 1 * * *-*-01 00:00:00
0 3 * * 1-5 Mon..Fri *-*-* 03:00:00

Rate nie bei einem Ausdruck. systemd-analyze calendar löst ihn auf und zeigt dir die nächsten Auslösezeitpunkte:

Konsole
$ systemd-analyze calendar --iterations=3 "Mon..Fri *-*-* 03:00:00"
  Original form: Mon..Fri *-*-* 03:00:00
Normalized form: Mon..Fri *-*-* 03:00:00
    Next elapse: Mon 2026-08-17 03:00:00 CEST
       From now: 8h left

Mehrere OnCalendar=-Zeilen in einer Unit addieren sich. Eine leere Zeile OnCalendar= setzt alle vorherigen zurück, das brauchst du bei Overrides via systemctl edit.

Timer aktivieren und verifizieren

Nach jeder Änderung an einer Unit-Datei muss systemd die Konfiguration neu einlesen. Danach aktivierst du den Timer, nicht den Service.

Konsole
$ sudo systemd-analyze verify /etc/systemd/system/backup-db.timer
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now backup-db.timer
Created symlink /etc/systemd/system/timers.target.wants/backup-db.timer → /etc/systemd/system/backup-db.timer.

Prüfe anschließend, ob der Timer registriert ist und wann er das nächste Mal feuert:

Konsole
$ systemctl list-timers backup-db.timer
NEXT                        LEFT     LAST PASSED UNIT             ACTIVATES
Mon 2026-08-17 02:30:00 CEST 9h left  n/a  n/a    backup-db.timer  backup-db.service

Steht dort eine plausible Zeit in NEXT, greift der Zeitplan. Teste die Service-Unit einmal manuell, ohne auf den Zeitpunkt zu warten:

Konsole
$ sudo systemctl start backup-db.service
$ systemctl status backup-db.service
● backup-db.service - Datenbank-Dump nach /var/backups
     Loaded: loaded (/etc/systemd/system/backup-db.service; static)
     Active: inactive (dead) since Sun 2026-08-16 17:42:11 CEST; 4s ago
    Process: 18422 ExecStart=/usr/local/bin/backup-db.sh (code=exited, status=0/SUCCESS)

status=0/SUCCESS und inactive (dead) sind bei Type=oneshot das erwartete Ergebnis. Die vollständige Ausgabe des Skripts liegt im Journal:

Konsole
$ journalctl -u backup-db.service --since "1 hour ago" --no-pager

Wenn du solche Läufe über mehrere Hosts hinweg auswerten willst, leite das Journal an einen zentralen Log-Empfänger weiter. Läuft die Datenbank selbst auf einer weiteren Cloud-VM mit dedizierten CPU-Kernen, trennst du Backup-Last und Produktivlast sauber voneinander.

Bestehende Cronjobs abschalten

Beide Mechanismen parallel laufen zu lassen führt zu doppelten Läufen. Entferne den alten Eintrag erst, nachdem der Timer einmal erfolgreich gefeuert hat:

Konsole
$ sudo crontab -u backup -l
$ sudo crontab -u backup -e
$ ls -l /etc/cron.d/ /etc/cron.daily/

Prüfe alle vier Orte: die User-Crontabs, /etc/crontab, /etc/cron.d/ und die Verzeichnisse cron.hourly bis cron.monthly.

Timer im User-Kontext betreiben

Für Jobs, die kein root und keinen Systemdienst brauchen, legst du die Units unter ~/.config/systemd/user/ ab und arbeitest mit --user:

Konsole
$ systemctl --user daemon-reload
$ systemctl --user enable --now backup-db.timer
$ sudo loginctl enable-linger $USER

loginctl enable-linger ist zwingend. Ohne diese Einstellung beendet systemd die User-Instanz mit der letzten Session, und der Timer feuert nach dem Logout nicht mehr.

Troubleshooting

Der Timer taucht nicht in list-timers auf. Meist wurde versehentlich der Service aktiviert. Prüfe mit systemctl is-enabled backup-db.timer. Liefert der Befehl disabled, hole sudo systemctl enable --now backup-db.timer nach.

Der Service scheitert mit 203/EXEC. Der Pfad in ExecStart= existiert nicht oder das Skript hat kein Executable-Bit. ExecStart verlangt einen absoluten Pfad, $PATH wird nicht ausgewertet. Setze sudo chmod +x /usr/local/bin/backup-db.sh.

Das Skript funktioniert in der Shell, aber nicht als Unit. systemd startet Services mit minimaler Umgebung, ohne die Variablen aus .bashrc oder /etc/profile. Setze benötigte Variablen explizit mit Environment="PGPASSFILE=/etc/backup/.pgpass" oder lade sie über EnvironmentFile=/etc/backup/env.

Der Zeitplan wird nicht akzeptiert. systemctl status backup-db.timer meldet dann einen Parse-Fehler. Prüfe den Ausdruck isoliert mit systemd-analyze calendar "<dein-ausdruck>", bevor du ihn in die Unit schreibst.

Fazit

Mit zwei Unit-Dateien, systemctl enable --now und einem Blick in systemctl list-timers ersetzt du einen Cronjob durch einen Timer, dessen Läufe nachvollziehbar im Journal stehen. Setze Persistent=true bei allen Jobs, deren Ausfall nach einem Reboot relevant ist, und prüfe jeden neuen OnCalendar-Ausdruck vorab mit systemd-analyze calendar. Als nächsten Schritt lohnt sich OnFailure= in der Service-Unit: Damit startest du bei einem Fehlschlag automatisch eine Benachrichtigungs-Unit.

Jetzt 200 € Guthaben sichern

Testen Sie Ihr Setup auf ccloud³

Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.

Ludwig Technische Redaktion

Schreibt bei centron über Linux-Administration, Container und Datenbanken – mit Fokus auf Anleitungen, die im Betrieb tatsächlich funktionieren.

Kategorie Linux Basics
Teilen
Noch offene Fragen?

Unser Team hilft Ihnen bei Ihrem konkreten Setup weiter – von Menschen, die die Plattform selbst betreiben.

War dieses Tutorial hilfreich?

Ihre Antwort wird anonym gespeichert und hilft uns, die Tutorials zu verbessern.

Kommentare

Noch keine Kommentare – stellen Sie die erste Frage zu diesem Tutorial.

Zum Kommentieren anmelden

Kommentare stehen centron-Kunden offen. Melden Sie sich in Ihrem Konto an, um eine Frage zu diesem Tutorial zu stellen.

Weiterlesen

Das könnte Sie auch interessieren

Jetzt kostenlos anfangen

Melden Sie sich an und erhalten Sie in den ersten 60 Tagen ein Guthaben von 200 € bei centron.

Dieses Werbeangebot gilt nur für neue Konten. Angebot ausschließlich für Gewerbetreibende.

Jetzt loslegen Sales kontaktieren