Cron vs. systemd-Timer: Geplante Aufgaben im Vergleich
$
/etc
Cron · systemd · Linux · Automatisierung
Cron vs. systemd-Timer
geplante Aufgaben im direkten Vergleich

Cron ist seit Jahrzehnten der Standard für geplante Aufgaben unter Linux, stößt aber bei Abhängigkeiten, Logging und Fehlerbehandlung an klare Grenzen. systemd-Timer bieten mit OnCalendar-Syntax, journalctl-Integration und automatischem Retry eine deutlich beobachtbarere Alternative, die sich mit einer konkreten Migration Schritt für Schritt einführen lässt.

15 Min. Lesezeit crontab · OnCalendar · journalctl · Retry systemd 245+ · Debian · Ubuntu · RHEL

1. Cron und systemd-Timer im Überblick

Cron ist seit den frühen Unix-Systemen der De-facto-Standard für zeitgesteuerte Aufgaben. Der Dienst crond liest periodisch Crontab-Dateien und startet Befehle zu festgelegten Zeitpunkten. Diese Einfachheit ist zugleich Stärke und Schwäche: Ein einzeiliger Eintrag genügt, aber alles, was über "starte Befehl X zu Zeit Y" hinausgeht, muss im Skript selbst nachgebaut werden, etwa Logging, Fehlerbehandlung oder Abhängigkeiten zu anderen Diensten.

systemd-Timer sind seit systemd 197 Teil des Init-Systems und ersetzen Cron nicht technisch, sondern konzeptionell. Ein Timer ist eine eigene Unit-Datei, die eine zugehörige .service-Unit zu bestimmten Zeitpunkten aktiviert. Dadurch erbt jeder Timer-Job automatisch alle Fähigkeiten von systemd: Ressourcenlimits, Abhängigkeitsverwaltung, strukturiertes Logging und Zustandsverfolgung über systemctl status. Wer bereits systemd für Dienste nutzt, wie es bei praktisch jeder modernen Linux-Distribution der Fall ist, bekommt diese Fähigkeiten für geplante Aufgaben quasi geschenkt.

Der Umstieg lohnt sich nicht bei jedem einfachen Ein-Zeiler, aber sobald ein Job produktionskritisch ist, Logging erfordert oder von anderen Diensten abhängt, zahlt sich die zusätzliche Konfigurationsdatei schnell aus. Die folgenden Abschnitte zeigen beide Ansätze im Detail und begleiten eine konkrete Migration von einem klassischen Cron-Job zu einer systemd-Timer-Unit.

2. Klassische crontab-Syntax und ihre Tücken

Die Crontab-Syntax besteht aus fünf Zeitfeldern gefolgt vom auszuführenden Befehl: Minute, Stunde, Tag des Monats, Monat, Wochentag. Der Eintrag 0 3 * * * bedeutet täglich um 3:00 Uhr. Nutzer-Crontabs werden mit crontab -e bearbeitet und liegen unter /var/spool/cron/crontabs/, während systemweite Jobs in /etc/cron.d/ mit einem zusätzlichen Benutzerfeld liegen. Diese Zweiteilung ist eine häufige Fehlerquelle, weil Einsteiger vergessen, dass Systemd-Cron-Dateien ein Feld mehr benötigen als persönliche Crontabs.

Eine der bekanntesten Fallen ist die minimalistische Umgebung, in der Cron Befehle ausführt. Anders als eine interaktive Shell setzt Cron standardmäßig nur eine sehr eingeschränkte PATH-Variable und lädt keine .bashrc. Ein Skript, das lokal per Terminal fehlerfrei läuft, schlägt im Cron-Kontext fehl, weil ein Binary wie node oder composer nicht gefunden wird. Deshalb gehört die explizite Pfadangabe oder das Setzen von PATH am Anfang der Crontab zur Grundausstattung jeder produktiven Konfiguration.


# crontab -e, user-level crontab entries
# Explicit PATH, because cron does not load .bashrc or /etc/profile
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@mironsoft.de

# m h dom mon dow command
0 3 * * *   /usr/local/bin/backup-db.sh >> /var/log/backup-db.log 2>&1
*/15 * * * * /usr/bin/php /var/www/app/bin/cron.php --queue=default
0 2 * * 0   /usr/local/bin/cleanup-logs.sh

# /etc/cron.d/app-jobs, system-wide entry needs an extra user field
0 4 * * *   www-data /usr/local/bin/reindex.sh >> /var/log/reindex.log 2>&1

3. Die strukturellen Grenzen von Cron

Der größte strukturelle Nachteil von Cron ist das völlige Fehlen einer Abhängigkeitsverwaltung. Cron kennt nur Uhrzeiten, keine Bedingungen. Wenn ein Backup-Job erst laufen darf, nachdem ein Datenbankexport abgeschlossen ist, muss diese Logik manuell im Skript nachgebildet werden, etwa durch Warteschleifen oder Lockfiles. Es gibt keinen nativen Weg, Cron mitzuteilen "starte Job B, sobald Job A erfolgreich war". Ebenso fehlt eine eingebaute Mechanik, um zu verhindern, dass ein Job startet, während die vorherige Instanz noch läuft, was bei langlaufenden Aufgaben zu überlappenden Prozessen führen kann.

Das zweite große Problem ist schwaches Logging. Cron protokolliert lediglich, dass ein Job gestartet wurde, meist als kurze Zeile in /var/log/syslog oder /var/log/cron. Die tatsächliche Ausgabe des Befehls geht verloren, sofern sie nicht explizit per >> logfile 2>&1 umgeleitet wird, und selbst dann fehlen strukturierte Metadaten wie Exit-Code, Laufzeit oder Ressourcenverbrauch. Wenn ein Job fehlschlägt, bleibt oft nur MAILTO als Benachrichtigungsweg, was in containerisierten oder Mail-losen Umgebungen praktisch nutzlos ist.

Ein drittes, oft übersehenes Problem: Cron bietet keinen Retry-Mechanismus. Schlägt ein Job wegen eines kurzzeitigen Netzwerkfehlers fehl, wartet Cron stur bis zum nächsten geplanten Zeitpunkt, egal ob das eine Minute oder eine Woche entfernt ist. Für kritische Wartungsaufgaben bedeutet das im schlimmsten Fall tagelange Ausfallzeit, bis jemand den Fehler manuell bemerkt und behebt.

4. systemd-Timer-Units: Aufbau und Funktionsweise

Ein systemd-Timer besteht immer aus zwei Dateien: einer .timer-Unit, die definiert, wann etwas ausgelöst wird, und einer gleichnamigen .service-Unit, die definiert, was ausgeführt wird. Diese Trennung ist bewusst so gestaltet, weil sie die Ausführungslogik komplett von der Zeitsteuerung entkoppelt. Der Service kann unabhängig vom Timer manuell getestet werden mit systemctl start backup-db.service, ohne auf den nächsten Zeitpunkt warten zu müssen. Das beschleunigt Debugging erheblich gegenüber Cron, wo ein Fehler oft erst beim nächsten geplanten Lauf sichtbar wird.

Beide Units liegen typischerweise unter /etc/systemd/system/ und werden nach jeder Änderung mit systemctl daemon-reload neu eingelesen. Aktiviert wird ausschließlich der Timer mit systemctl enable --now backup-db.timer, niemals der Service direkt, da sonst die Zeitsteuerung umgangen würde. Der aktuelle Status inklusive nächstem geplanten Lauf lässt sich jederzeit mit systemctl list-timers einsehen, was einen Überblick liefert, den Cron in dieser Form nicht bietet.


# /etc/systemd/system/backup-db.service
[Unit]
Description=Nightly database backup
Wants=network-online.target
After=network-online.target mysql.service

[Service]
Type=oneshot
User=www-data
ExecStart=/usr/local/bin/backup-db.sh
TimeoutStartSec=1800

# /etc/systemd/system/backup-db.timer
[Unit]
Description=Run backup-db.service nightly at 03:00

[Timer]
# Equivalent to cron "0 3 * * *"
OnCalendar=*-*-* 03:00:00
# Catch up missed runs after downtime (e.g. server was off)
Persistent=true
# Spread load across a random window to avoid thundering herd
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

5. OnCalendar-Syntax im Detail

Die OnCalendar-Direktive ersetzt die fünf kryptischen Cron-Felder durch eine lesbarere Syntax nach dem Schema Wochentag Jahr-Monat-Tag Stunde:Minute:Sekunde, wobei jeder Teil optional ist und Platzhalter wie * unterstützt. OnCalendar=*-*-* 03:00:00 entspricht dem Cron-Ausdruck 0 3 * * *, ist aber ohne Nachschlagen verständlich. Komplexere Muster wie "jeden ersten Montag im Monat" oder "alle 15 Minuten zwischen 8 und 18 Uhr an Werktagen" lassen sich mit Mon *-*-1..7 08..18:00/15:00 abbilden, was in reiner Cron-Syntax kaum lesbar wäre.

Ein praktischer Vorteil gegenüber Cron ist OnBootSec und OnUnitActiveSec für relative Zeitangaben, etwa "10 Minuten nach Systemstart" oder "alle 6 Stunden seit dem letzten erfolgreichen Lauf", unabhängig von festen Uhrzeiten. Mit systemd-analyze calendar "Mon *-*-1..7 08:00:00" lässt sich jeder OnCalendar-Ausdruck vor dem Deployment testen und zeigt den nächsten tatsächlichen Ausführungszeitpunkt an, ein Werkzeug, für das es bei Cron keine offizielle Entsprechung gibt.

6. Logging und journalctl-Integration

Der entscheidende Beobachtbarkeitsvorteil von systemd-Timern liegt in der automatischen Integration mit journald. Jede Ausgabe eines per Timer gestarteten Services, sowohl stdout als auch stderr, landet ohne zusätzliche Konfiguration strukturiert im Journal, inklusive Zeitstempel, Exit-Code, Prozess-ID und zugehöriger Unit. Es muss kein >> logfile 2>&1 mehr an jeden Befehl angehängt werden, und es entsteht keine Logdatei-Rotation, um die sich ein Admin manuell kümmern muss, weil journald das Retention-Management übernimmt.

Mit journalctl -u backup-db.service --since today lassen sich alle Läufe eines bestimmten Tages filtern, mit journalctl -u backup-db.service -f live mitverfolgen. Besonders wertvoll ist systemctl status backup-db.service, das auf einen Blick zeigt, ob der letzte Lauf erfolgreich war, wie lange er dauerte und welchen Exit-Code er zurückgab, Informationen, die bei Cron erst mühsam aus verstreuten Logdateien rekonstruiert werden müssten. Für Monitoring-Integrationen lässt sich journald zudem per ForwardToSyslog oder systemd-journal-remote an zentrale Log-Aggregatoren wie Loki oder ELK anbinden.


# Inspect the last run and its exit code at a glance
systemctl status backup-db.service

# Full log output of the service, newest first
journalctl -u backup-db.service --since today --no-pager

# Follow logs live while the service runs
journalctl -u backup-db.service -f

# List all timers with their next and last trigger time
systemctl list-timers --all

7. Automatischer Retry und Abhängigkeiten

Wo Cron bei einem fehlgeschlagenen Job schlicht bis zum nächsten Zeitpunkt wartet, bietet die Service-Unit hinter einem Timer volle Zugriff auf die systemd-Restart-Logik. Mit Restart=on-failure, RestartSec=30 und StartLimitBurst=3 versucht systemd automatisch bis zu drei erneute Starts im Abstand von 30 Sekunden, bevor der Job endgültig als fehlgeschlagen markiert wird. Das fängt genau die Klasse von Fehlern ab, die in der Praxis am häufigsten vorkommt: kurzzeitige Netzwerkprobleme, ein noch nicht bereiter Datenbank-Container oder ein vorübergehend gesperrtes Lockfile.

Abhängigkeiten werden über die Standard-Unit-Direktiven Requires, After und Wants abgebildet. Ein Job, der erst starten darf, wenn MySQL erreichbar ist, bekommt After=mysql.service und Requires=mysql.service in der Service-Unit, wodurch systemd automatisch die Startreihenfolge sicherstellt. Für Kettenverarbeitung, bei der Job B erst nach erfolgreichem Job A laufen soll, lässt sich OnSuccess=job-b.service in der Service-Unit von Job A setzen, ein natives Feature, für das Cron-Nutzer sonst externe Orchestrierungstools wie Airflow bemühen müssten.


# /etc/systemd/system/backup-db.service (with retry and dependency handling)
[Unit]
Description=Nightly database backup with retry
Requires=mysql.service
After=network-online.target mysql.service
OnFailure=notify-admin@%n.service

[Service]
Type=oneshot
User=www-data
ExecStart=/usr/local/bin/backup-db.sh
Restart=on-failure
RestartSec=30
StartLimitBurst=3
StartLimitIntervalSec=600
TimeoutStartSec=1800

8. Praktische Migration: von Cron-Job zu Timer-Unit

Der Umstieg gelingt am besten Job für Job, nicht als größerer Sprung für die gesamte Crontab. Als Beispiel dient ein Datenbank-Backup, das bisher täglich um 3:00 Uhr per Cron lief. Zunächst wird der bestehende Cron-Eintrag identifiziert und dokumentiert, danach die Service-Unit angelegt, die exakt denselben Befehl ausführt wie zuvor der Cron-Job. Wichtig ist, den Job zunächst parallel zu testen, bevor der alte Cron-Eintrag entfernt wird, um sicherzustellen, dass Berechtigungen, Umgebungsvariablen und Arbeitsverzeichnis korrekt übertragen wurden.

Nach dem Anlegen beider Unit-Dateien folgt der eigentliche Testlauf: systemctl daemon-reload lädt die neuen Units, systemctl start backup-db.service führt den Service sofort manuell aus, unabhängig vom Timer, und journalctl -u backup-db.service zeigt sofort, ob alles wie erwartet funktioniert hat. Erst wenn dieser manuelle Testlauf zuverlässig erfolgreich ist, wird der Timer mit systemctl enable --now backup-db.timer aktiviert und der alte Cron-Eintrag mit crontab -e entfernt oder auskommentiert, damit der Job nicht doppelt läuft.

Für eine ganze Crontab mit vielen Einträgen empfiehlt sich ein schrittweiser Rollout: unkritische Jobs zuerst migrieren, um Erfahrung mit der neuen Struktur zu sammeln, kritische Produktionsjobs zuletzt, wenn das Team mit systemctl list-timers, journalctl-Filtern und dem Debugging von Timer-Units vertraut ist. Ein begleitendes Migrationsprotokoll, das alte Cron-Zeile und neue Timer-Unit gegenüberstellt, erleichtert später die Fehlersuche erheblich.


# 1. Document the existing cron entry before touching anything
crontab -l | grep backup-db
# 0 3 * * * /usr/local/bin/backup-db.sh >> /var/log/backup-db.log 2>&1

# 2. Create service + timer unit (see sections above), then reload
sudo systemctl daemon-reload

# 3. Test the service manually, independent of the timer schedule
sudo systemctl start backup-db.service
journalctl -u backup-db.service --since "5 minutes ago" --no-pager

# 4. Only after a successful manual run: enable the timer
sudo systemctl enable --now backup-db.timer
systemctl list-timers backup-db.timer

# 5. Remove the now-redundant cron entry
crontab -e   # comment out or delete the old line

9. Cron und systemd-Timer im direkten Vergleich

Beide Ansätze haben ihre Berechtigung, aber für produktionskritische Aufgaben mit Fehlerbehandlung, Logging und Abhängigkeiten ist der Unterschied erheblich. Die folgende Tabelle stellt die zentralen Eigenschaften gegenüber.

Merkmal Cron systemd-Timer
Logging Manuell per Umleitung, unstrukturiert Automatisch strukturiert in journald
Retry bei Fehlschlag Kein Mechanismus, wartet bis nächster Slot Restart=on-failure mit konfigurierbarem Intervall
Abhängigkeiten Nicht vorhanden, muss im Skript nachgebaut werden After, Requires, OnSuccess nativ verfügbar
Verpasste Läufe nach Downtime Werden ignoriert, kein Nachholen Persistent=true holt verpasste Läufe nach
Statusabfrage Keine eingebaute Übersicht systemctl list-timers, systemctl status
Ressourcenlimits Nicht vorhanden MemoryMax, CPUQuota über Service-Unit
Einstiegshürde Eine Zeile, sofort verständlich Zwei Unit-Dateien, mehr Konfigurationsaufwand

Für einfache, unkritische Wartungsjobs bleibt Cron aufgrund seiner Einfachheit eine legitime Wahl. Sobald aber Beobachtbarkeit, Fehlertoleranz oder Abhängigkeiten zu anderen Diensten gefordert sind, überwiegen die Vorteile von systemd-Timern deutlich, insbesondere weil auf praktisch jedem modernen Linux-Server ohnehin systemd läuft und keine zusätzliche Software installiert werden muss.

Mironsoft

Server-Automatisierung, Monitoring und Deployment-Infrastruktur

Geplante Aufgaben, die zuverlässig laufen und beobachtbar bleiben?

Wir analysieren bestehende Cron-Jobs, migrieren produktionskritische Aufgaben zu systemd-Timern und richten Logging sowie Monitoring ein, damit Fehlschläge nicht mehr unbemerkt bleiben.

Cron-Audit

Bestehende Crontabs auf kritische Jobs und Migrationsbedarf prüfen

Timer-Migration

Cron-Jobs schrittweise zu systemd-Timer-Units mit Retry-Logik überführen

Monitoring-Setup

journald-Anbindung an zentrale Log-Systeme und Alerting bei Fehlschlägen

10. Zusammenfassung

Cron bleibt für einfache, unkritische Aufgaben ein pragmatisches Werkzeug: ein Eintrag, keine Zusatzkonfiguration, sofort verständlich. Sobald aber Logging, Fehlerbehandlung, Abhängigkeiten zu anderen Diensten oder automatischer Retry gefordert sind, zeigt Cron seine strukturellen Grenzen deutlich. systemd-Timer lösen diese Probleme nativ: OnCalendar ersetzt die kryptische Fünf-Felder-Syntax durch lesbare Ausdrücke, journald liefert strukturiertes Logging ohne Zusatzaufwand, und Restart=on-failure fängt transiente Fehler automatisch ab, ohne dass ein Job bis zum nächsten geplanten Zeitpunkt warten muss.

Die Migration muss nicht auf einmal geschehen. Ein schrittweiser Umstieg, beginnend mit unkritischen Jobs und einer sauberen Testphase je Service-Unit, minimiert das Risiko und baut gleichzeitig Vertrauen und Erfahrung mit der neuen Struktur auf. Auf praktisch jedem modernen Linux-Server steht systemd bereits zur Verfügung, sodass keine zusätzliche Software nötig ist, nur eine bewusste Entscheidung, welche Jobs von der besseren Beobachtbarkeit am meisten profitieren.

Cron vs. systemd-Timer: Das Wichtigste auf einen Blick

Cron-Grenzen

Keine Abhängigkeitsverwaltung, schwaches Logging, kein Retry bei Fehlschlag. Fehler bleiben oft unbemerkt.

OnCalendar-Syntax

Lesbare Zeitausdrücke statt kryptischer Fünf-Felder-Syntax, testbar mit systemd-analyze calendar.

journalctl-Integration

Strukturiertes Logging ohne manuelle Umleitung, sofortiger Statusüberblick per systemctl status.

Retry & Abhängigkeiten

Restart=on-failure, After und Requires lösen Probleme, die Cron gar nicht kennt.

11. FAQ: Cron vs. systemd-Timer

1Ersetzen systemd-Timer Cron vollständig?
Technisch ja, Cron funktioniert weiterhin parallel. Für kritische Jobs mit Logging- und Retry-Bedarf sind Timer die robustere Wahl, für triviale Aufgaben reicht Cron weiterhin.
2Was bedeutet OnCalendar=*-*-* 03:00:00?
Entspricht Cron 0 3 * * *, also täglich um 3:00 Uhr. Testbar vorab mit systemd-analyze calendar.
3Wie sehe ich den Status eines Timer-Jobs?
systemctl status name.service zeigt letzten Lauf, Exit-Code und Laufzeit. journalctl -u name.service --since today liefert die volle Ausgabe.
4Was macht Persistent=true?
Holt einen verpassten Lauf nach Downtime beim nächsten Boot nach. Cron bietet dafür keine Entsprechung.
5Wie konfiguriere ich automatischen Retry?
Restart=on-failure, RestartSec für das Intervall, StartLimitBurst für die maximale Versuchsanzahl in der Service-Unit.
6Warum schlägt mein Cron-Job fehl, obwohl er lokal läuft?
Meist eine eingeschränkte PATH-Variable, weil Cron keine Shell-Profile lädt. Explizite Pfade und PATH-Zeile in der Crontab beheben das.
7Kann ein Timer-Job von einem anderen Dienst abhängen?
Ja, über After und Requires. Ein Job kann so erst starten, wenn ein anderer Dienst läuft, mit Cron nicht nativ möglich.
8Wie migriere ich risikoarm?
Units anlegen, Service manuell testen, journalctl prüfen, erst nach Erfolg den Timer aktivieren und den Cron-Eintrag entfernen.
9Brauche ich zusätzliche Software?
Nein, systemd-Timer sind Teil des Init-Systems und auf praktisch jeder modernen Distribution bereits vorhanden.
10Lohnt sich der Umstieg für kleine Jobs?
Nicht zwingend. Für triviale Aufgaben ohne Logging- oder Abhängigkeitsbedarf bleibt eine Crontab-Zeile die einfachste Lösung.