Linux bietet zwei Wege, Dienste zeitgesteuert auszuführen: cron und systemd-Timer. Beide lösen dieselbe Aufgabe, unterscheiden sich aber in Zuverlässigkeit und Nachvollziehbarkeit deutlich. Dieser Artikel zeigt beide und wann welcher der richtige ist.
cron: die klassische Lösung
Der Dienst cron liest Zeitpläne und startet Befehle. Die Syntax kennt fünf Felder plus den Befehl:
# ┌──────── Minute (0-59)
# │ ┌────── Stunde (0-23)
# │ │ ┌──── Tag im Monat (1-31)
# │ │ │ ┌── Monat (1-12 oder Namen)
# │ │ │ │ ┌ Wochentag (0-7, wobei 0 und 7 = Sonntag)
# │ │ │ │ │
# * * * * * befehl
# Beispiele
0 3 * * * /usr/local/bin/backup.sh # taeglich 03:00
*/15 * * * * /usr/local/bin/check.sh # alle 15 Minuten
0 */6 * * * /usr/local/bin/sync.sh # alle 6 Stunden
30 2 * * 1 /usr/local/bin/wochenbericht.sh # Montags 02:30
0 0 1 * * /usr/local/bin/monatslauf.sh # am 1. jeden Monats
@reboot /usr/local/bin/startvorgang.sh # beim Systemstart
@daily /usr/local/bin/tagessache.sh
@weekly /usr/local/bin/wochensache.sh
# Verwalten
crontab -l # eigene Zeitplaene anzeigen
crontab -e # bearbeiten
crontab -l -u max # die eines anderen Benutzers (als root)
crontab -r # ALLE loeschen (Vorsicht!)
# Systemweite Verzeichnisse
ls /etc/cron.d/ # eigene Dateien im gleichen Format
ls /etc/cron.daily/ # Skripte, die taeglich laufen
ls /etc/cron.hourly/
ls /etc/cron.weekly/
# Log pruefen (auf modernen Systemen im Journal)
journalctl -u cron --since today
journalctl -u crond --since today
Die Fallen bei cron
cron ist einfach, aber es gibt fünf Punkte, an denen fast jeder einmal scheitert:
- Andere Umgebung. cron startet mit minimaler Umgebung: kein
PATHwie in der Shell, keine Variablen aus.bashrc. Lösung: Immer vollständige Pfade verwenden und benötigte Variablen selbst setzen. - Ausgaben landen in einer Mail. Jede Ausgabe von
stdoutoderstderrwird per Mail an den Benutzer geschickt. Fehlt ein Mailversand, sammelt sich alles an. Lösung: Ausgaben gezielt umleiten:>> /var/log/backup.log 2>&1. - Läuft nicht bei verpasster Zeit. War der Rechner aus, wird der Lauf nicht nachgeholt. Lösung: Wer das braucht, verwendet Timer oder eine Sperrdatei.
- Überlappende Läufe. Braucht ein Lauf länger als das Intervall, starten mehrere gleichzeitig. Lösung:
flockverwenden. %in crontab. Ein Prozentzeichen im Befehl wird als Zeilenumbruch interpretiert. Lösung: maskieren (\%) oder in ein Skript auslagern.
# Sauberer crontab-Eintrag mit vollstaendiger Umgebung und Sperre
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
# Jede Minute pruefen, aber nur ein Lauf gleichzeitig
* * * * * flock -n /var/lock/monitor.lock /usr/local/bin/monitor.sh >> /var/log/monitor.log 2>&1
# Taeglich mit Zeitstempel
0 3 * * * /usr/local/bin/backup.sh 2>&1 | logger -t backup
systemd-Timer: der moderne Weg
Ein Timer ist eine eigenständige Einheit (Unit) mit zwei Teilen: dem Zeitplan und dem Dienst, der ausgeführt wird.
# /etc/systemd/system/backup.service - WAS ausgefuehrt wird
[Unit]
Description=Taegliche Sicherung
Wants=network-online.target
[Service]
Type=oneshot
User=backup
Group=backup
WorkingDirectory=/srv/backup
ExecStart=/usr/local/bin/backup.sh
# Ausgabe ins Journal
StandardOutput=journal
StandardError=journal
# Ressourcen begrenzen
MemoryMax=1G
IOWeight=50
# Bei Fehler nicht endlos wiederholen
Restart=no
# /etc/systemd/system/backup.timer - WANN ausgefuehrt wird
[Unit]
Description=Taegliche Sicherung um 03:00
[Timer]
# Zeitplaene nach Kalenderschreibweise von systemd
OnCalendar=*-*-* 03:00:00
# Verpasste Laeufe nachholen! (der grosse Vorteil)
Persistent=true
# Zufaellige Verzoegerung - verhindert Lastspitzen
RandomizedDelaySec=600
# Nach dem Aufwachen nachholen
OnBootSec=15min
[Install]
WantedBy=timers.target
# Aktivieren
systemctl daemon-reload
systemctl enable --now backup.timer
# Pruefen
systemctl list-timers --all
systemctl status backup.timer
journalctl -u backup.service -n 50
journalctl -u backup.service --since '2 days ago'
# Zeitplan pruefen, bevor er scharf ist
systemd-analyze calendar '*-*-* 03:00:00'
systemd-analyze calendar 'Mon..Fri *-*-* 08:00'
systemd-analyze calendar '*-*-01 00:00:00'
# Sofort ausloesen zum Testen
systemctl start backup.service
# Alle Timer mit naechstem Lauf
systemctl list-timers
Kalenderschreibweise von systemd
*-*-* 03:00:00 taeglich um 03:00
*-*-* 03:00 dasselbe
Mon..Fri *-*-* 08:30:00 werktags
Mon *-*-* 02:30:00 montags
*-*-01 04:00:00 am Monatsersten
*-*-* 00/6:00:00 alle 6 Stunden ab Mitternacht
Sat,Sun *-*-* 05:00:00 Wochenende
*-*-* *:00:00 stündlich (praktisch: *:0/15 für alle 15 Minuten)
*-*-* *:0/15:00 alle 15 Minuten
2000-01-01 00:00:00 einmalig
minutely / hourly / daily Kurzformen
Vergleich und Empfehlung
- Vergangene Läufe: Timer holen mit
Persistent=truenach, cron nicht. Das ist in der Praxis das wichtigste Argument – eine Sicherung, die wegen eines Neustarts ausfiel und nicht nachgeholt wird, fehlt. - Protokolle: Timer und Dienste schreiben ins Journal. Damit ist die Ausgabe durchsuchbar, gefiltert und in der Länge begrenzt:
journalctl -u backup.servicestatt einer verstreuten Protokolldatei. - Abhängigkeiten: Ein Timer kann
Wants=network-online.targetfordern; cron kennt keine Abhängigkeiten. - Ressourcen: Timer können Speicher, Prozessorzeit und Ein-/Ausgabe begrenzen. cron kann das nicht.
- Zufällige Verzögerung:
RandomizedDelaySecist bei vielen Systemen mit gleichem Zeitplan sehr nützlich. - Einfachheit: Für ein kurzes Skript auf einem Einzelrechner ist ein crontab-Eintrag nach wie vor schneller geschrieben.
Faustregel: Für Sicherungen, Wartungsläufe und alles, was nachweislich stattgefunden haben muss, sind systemd-Timer die richtige Wahl. Für einen kurzen, unkritischen Befehl auf einem Einzelplatz genügt cron.
Sicherstellen, dass es wirklich läuft
Ein geplanter Auftrag, der stillschweigend nichts tut, ist ein häufiges Problem. Drei Maßnahmen:
# 1. Erfolg im Skript protokollieren
#!/usr/bin/env bash
set -euo pipefail
echo "$(date -Is) START"
/usr/local/bin/backup.sh --full
echo "$(date -Is) OK"
# /etc/systemd/system/backup.timer - zusaetzlich: bei Fehler sichtbar werden
[Unit]
Description=Taegliche Sicherung
OnFailure=fehler-melden@%n.service
# 2. Ergebnis pruefen - der Lauf muss sich melden
# Zuverlaessig ist eine Meldung NUR bei Fehler:
# [Service]
# ExecStart=/usr/local/bin/backup.sh
# ExecStartPost=/usr/local/bin/erfolg-melden.sh
# Besser noch: Ein Monitor prueft, ob die Sicherung frisch ist.
# 3. Ergebnis von aussen pruefen (die beste Methode)
# Ein unabhaengiger Check (z.B. Health-Check) meldet,
# wenn die Sicherung aelter als 26 Stunden ist.
Die dritte Maßnahme ist die wirkungsvollste: Nicht der Auftrag meldet Erfolg, sondern ein unabhängiger Beobachter meldet Ausfall. Damit wird auch der Fall erkannt, in dem der Auftrag gar nicht erst gestartet ist – etwa weil der Timer versehentlich deaktiviert wurde.
Verwandte Grundlagen: Systemd: Dienste verwalten, Cron und Crontab, Backup-Test, Monitoring.