Cron-Expressions verstehen und richtig schreiben
Cron-Expressions sind eine seltsame Mischung aus genial und unverständlich. Fünf Felder mit Zahlen und Sonderzeichen reichen aus, um beliebig komplexe Zeitpläne zu beschreiben — vom täglichen Backup um 03:15 Uhr bis zum »jeden zweiten Dienstag im Quartal". Wer die Syntax einmal verinnerlicht hat, kommt nie wieder davon weg. Wer sie nicht kennt, vermutet Magie. Dieser Artikel räumt auf.
Eine kurze Geschichte
cron geht zurück auf Unix Version 7 von 1979. Geschrieben von Brian Kernighan, war es ein einfacher Daemon, der jede Minute eine Konfigurationsdatei las und passende Befehle ausführte. Paul Vixie schrieb 1987 die heute weithin verwendete Variante (Vixie-Cron), die das Konzept um Benutzer-Crontabs, Umgebungsvariablen und das @reboot-Schlüsselwort erweiterte.
Heute existieren viele Cron-Dialekte: GNU/Linux-Distributionen liefern meist Vixie-Cron oder eine Variante davon. macOS hat lange auf cron gesetzt, ist aber inzwischen bei launchd. Kubernetes hat CronJobs mit Unix-Syntax. Java-Frameworks wie Quartz haben einen erweiterten 6/7-Feld-Dialekt eingeführt. Die Grundideen sind ähnlich — Details unterscheiden sich.
Die 5-Feld-Syntax
Eine klassische Unix-Cron-Expression besteht aus fünf durch Leerzeichen getrennten Feldern, gefolgt vom auszuführenden Befehl. Die Felder, in dieser Reihenfolge:
- Minute (0–59)
- Stunde (0–23)
- Tag des Monats / Day of Month (1–31)
- Monat (1–12 oder JAN–DEC)
- Tag der Woche / Day of Week (0–6, 0 = Sonntag, oder SUN–SAT)
Sonderzeichen
In jedem Feld erlauben mehrere Sonderzeichen, Wertebereiche zu beschreiben:
- * — beliebiger Wert. Im Minutenfeld bedeutet * jede Minute.
- , — Aufzählung. Beispiel: 0,30 im Minutenfeld führt zu Minute 0 und Minute 30.
- - — Bereich. Beispiel: 9-17 im Stundenfeld bedeutet 09:00, 10:00, ..., 17:00.
- / — Schrittweite. Beispiel: */15 im Minutenfeld bedeutet alle 15 Minuten (0, 15, 30, 45). Auch in Kombination mit Bereichen: 0-30/5 bedeutet 0, 5, 10, ..., 30.
- ? — »kein konkreter Wert". Wird nur von Quartz unterstützt und entweder im Day-of-Month- oder Day-of-Week-Feld benutzt, wenn man im anderen Feld bereits eine Vorgabe gemacht hat.
Schlüsselwörter
Statt der fünf Felder kann man auch Aliase nutzen — kürzer und besser lesbar:
- @yearly / @annually — entspricht 0 0 1 1 * (einmal im Jahr, am 1. Januar um Mitternacht)
- @monthly — 0 0 1 * * (jeden Ersten des Monats um Mitternacht)
- @weekly — 0 0 * * 0 (jeden Sonntag um Mitternacht)
- @daily / @midnight — 0 0 * * * (jeden Tag um Mitternacht)
- @hourly — 0 * * * * (jede Stunde zur vollen Stunde)
- @reboot — einmal beim Start des Cron-Daemons (also typischerweise beim Hochfahren des Systems)
Praktische Beispiele
- */15 * * * * — alle 15 Minuten
- 0 3 * * 0 — jeden Sonntag um 03:00 Uhr
- 30 8 * * 1-5 — montags bis freitags um 08:30 Uhr
- 0 0 1 */3 * — am 1. jedes Quartals um Mitternacht
- 15 9-17 * * 1-5 — montags bis freitags zur 15. Minute jeder Stunde zwischen 9 und 17 Uhr
Quartz und andere Erweiterungen
Java-Scheduler wie Quartz nutzen eine erweiterte Syntax mit sechs oder sieben Feldern: ein zusätzliches Sekundenfeld vorne, optional ein Jahresfeld hinten. Außerdem gibt es Sonderzeichen wie L (last day of month), W (nearest weekday) und # (nth occurrence of weekday in month). Quartz erlaubt sich auch das ?-Zeichen, das im Unix-Cron unbekannt ist.
Wer eine Cron-Expression aus einem Java-/Spring-Tutorial in eine Linux-Crontab kopiert, läuft Gefahr, dass das zusätzliche Sekundenfeld als Minute interpretiert wird — und der Job zu völlig anderen Zeiten läuft. Vor dem Übertragen lohnt sich immer der Blick auf die Dokumentation der Ziel-Plattform.
Häufige Stolperfallen
- Zeitzone: cron nutzt standardmäßig die lokale Systemzeitzone. Wer Server weltweit verteilt betreibt, sollte explizit eine Zeitzone setzen (z. B. via CRON_TZ in Vixie-Cron) — sonst läuft das »nächtliche Backup" auf Servern in Tokio plötzlich am Nachmittag deiner Region.
- Sommerzeit: an Tagen mit Zeitumstellung kann ein Job in einer ausgelassenen Stunde gar nicht laufen oder in einer doppelten Stunde zweimal. Wer das nicht akzeptieren kann, sollte UTC als Cron-Zeitzone wählen.
- Langsame Jobs: cron startet den nächsten Job zur geplanten Zeit, egal ob der vorherige noch läuft. Bei sich überlappenden Backups oder Synchronisierungen lohnt sich ein Lock-File oder ein Wrapper wie flock.
- DoM und DoW sind verknüpft per OR, nicht AND: 0 0 15 * 1 läuft sowohl am 15. jedes Monats als auch an jedem Montag. Wer »nur Montag, der 15. ist" meint, braucht entweder ein zusätzliches Skript-Check oder eine Quartz-ähnliche Syntax.
Meine drei schmerzhaftesten Cron-Fehler
Cron-Fehler Nummer 1 (2016): ich hatte einen Cron-Job geschrieben, der jeden Morgen um 4 Uhr ein 200-GB-Datenbank-Backup auslöste — und dann gleich um 4:05 ein Skript, das auf das fertige Backup zugriff. Klingt logisch, war es nicht. Die Backup-Dauer wuchs mit den Daten, von 3 auf irgendwann 12 Minuten. Drei Wochen lang verarbeitete mein Folge-Skript Teile vom Vortags-Backup, weil das aktuelle noch nicht fertig war. Der Bug fiel erst auf, als ein Kunde berichtete, dass die Auswertungen nicht mehr aktuell wirkten.
Cron-Fehler Nummer 2 (2019): Server-Migration auf eine neue Maschine, Cron-Tab kopiert, alles funktioniert — dachte ich. Eine Woche später war die Festplatte voll. Ein Log-Rotate-Skript, das jede Stunde lief, hatte nie die alten Logs gelöscht, weil der Pfad im neuen Server anders war. tar: cannot open-Fehler wurden zwar geloggt, aber niemand hatte je das Log gelesen. Lehre: jeder Cron-Job braucht eine sichtbare Fehlerbenachrichtigung, nicht nur einen Log-Eintrag.
Cron-Fehler Nummer 3 (2022): Sommerzeit-Umstellung. Mein Cron-Job lief jede Nacht um 02:30 — und genau am 30.03.2022 wurde diese Uhrzeit übersprungen, weil die Schaltsekunde um 02:00 → 03:00 sprang. Ein wichtiges Reporting-Skript wurde an diesem Tag schlicht nicht ausgeführt. Niemand merkte es, weil das Dashboard am Folgetag »leer aussah, aber irgendwie« funktionierte. Seitdem setze ich kritische Jobs auf 04:30 oder 05:00 — sicher außerhalb des DST-Risiko-Fensters. Plus: UTC verwenden wo immer möglich, um das Problem komplett zu umgehen.
systemd-Timer: warum sie cron in den meisten Fällen schlagen
Seit etwa 2018 hat sich auf modernen Linux-Distributionen (Ubuntu 20.04+, Debian 11+, RHEL 8+) systemd-Timer als ernsthafte Alternative zu cron etabliert. Statt zwei Komponenten (cron + Skript) hast du zwei Unit-Files: eine .service-Unit, die definiert, was ausgeführt wird, und eine .timer-Unit, die definiert, wann. Klingt nach mehr Aufwand, ist aber deutlich robuster.
Die wichtigsten Vorteile gegenüber cron: journalctl-Integration (alle Logs vereinheitlicht und durchsuchbar), Persistence (verpasste Runs werden nach Reboot nachgeholt, mit Persistent=true), Randomization (RandomizedDelaySec= verteilt Cluster-Jobs, sodass nicht alle Server gleichzeitig loslegen), Calendar-Specs sind ausdrucksstärker als cron (»jeden ersten Werktag im Monat um 9 Uhr« geht ohne Skript-Logik direkt im Timer), und Dependency Management (ein Timer kann auf einen anderen Service warten).
Wann cron trotzdem besser bleibt: bei Shared Hosting (kein root-Zugriff für systemctl), bei sehr einfachen einzeiligen Jobs (»jede Stunde diesen Endpoint anpingen«), in Docker-Containern ohne systemd. Für alle anderen Fälle in einem modernen Linux-Setup ist systemd-Timer die bessere Wahl. Ich migriere persönlich seit 2021 alle neuen Jobs auf systemd, behalte aber bestehende cron-Jobs in Ruhe — »funktioniert« ist ein guter Zustand.
Cron in Kubernetes: das CronJob-Resource
Wer in Kubernetes-Clustern arbeitet, kennt das CronJob-Resource. Es nutzt die gleiche Cron-Syntax, läuft aber als Container-Pod und ist hochverfügbar — wenn ein Knoten ausfällt, übernimmt ein anderer. Die wichtigste Falle: concurrencyPolicy. Standardwert ist Allow, was bedeutet: wenn ein Job länger läuft als das Intervall, startet ein zweiter parallel. Bei DB-Migrationen oder Backup-Skripten ist das fatal. Konsequente Empfehlung: Forbid oder Replace für alle nicht-idempotenten Jobs setzen.
Zweite K8s-Falle: startingDeadlineSeconds. Wenn der Cluster zum geplanten Zeitpunkt überlastet ist oder neu startet, kann ein Job verschoben werden. Ohne startingDeadlineSeconds versucht K8s den Job unbegrenzt lange nachzuholen — mit dem Wert kannst du sagen »wenn er nicht innerhalb von 10 Minuten startet, überspringen«. Für tägliche Backups: startingDeadlineSeconds: 3600 (1 Stunde) als sinnvoller Default. Für stündliche Cleanups: startingDeadlineSeconds: 300.
Wenn ein Cron-Job nicht läuft: 5-Punkte-Debug-Plan
Cron-Bugs sind besonders frustrierend, weil sie unsichtbar passieren — nichts schlägt fehl, das Skript wurde einfach nie ausgeführt. Hier mein Workflow, sortiert nach Wahrscheinlichkeit:
- Log prüfen.
grep CRON /var/log/syslogoderjournalctl -u cron. Wenn da gar keine Einträge zur geplanten Zeit sind: Crontab ist nicht aktiv oder der Job läuft auf einem anderen User-Account.crontab -l(für den aktuellen User),sudo crontab -u www-data -l(für andere User). - Environment-Variablen. Cron läuft mit einem minimalen Environment — kein
PATHwie in deiner Shell, keinLANG, oft keine User-spezifischen ENVs. Wenn dein Skriptphp artisan ...aufruft und »command not found« kommt, ist der PATH der Übeltäter. Lösung: vollständige Pfade nutzen (/usr/bin/php /var/www/artisan ...) oder am Anfang der CrontabPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binsetzen. - Arbeits-Verzeichnis. Cron startet im Home-Verzeichnis des User, nicht im Verzeichnis des Skripts. Relative Pfade brechen. Lösung:
cd /pfad/zum/projekt && ./skript.sh, oder im Skript selbstcd $(dirname "$0")als erste Zeile. - Lock-Files. Wenn ein vorheriger Run noch läuft und dein Skript ein Lock-File prüft, wird der neue Run übersprungen.
ls -la /var/run/myscript.lockzeigt, ob ein Lock existiert. Wenn die Datei alt ist und der Prozess längst tot, manuell löschen. - MAILTO-Notifications. Cron kann Output und Fehler per Mail senden, wenn
[email protected]am Anfang der Crontab steht. Voraussetzung: lokaler MTA (postfix/sendmail) ist konfiguriert. Wer das macht, bekommt jeden stillen Fehler proaktiv — meine Standard-Konfiguration für alle Produktionssysteme.
In 90 % der Fälle ist es einer der ersten drei Punkte. Wer einmal eine Mail- oder Slack-Notification für Cron-Failures aufgesetzt hat, hat den Job-Status-Stress aus seinem Leben verbannt. Plus: ein wöchentlicher »alle Cron-Jobs liefen wie geplant«-Health-Check als zusätzlicher Job ist eine geringe Mehraufwand für viel Ruhe.
Häufige Fragen
Wie teste ich eine Cron-Expression, ohne zu warten?
Cron-Generatoren und -Parser können dir die nächsten paar Ausführungszeitpunkte einer Expression vorrechnen. Auf Servern kannst du den Befehl mit kurzem Cron-Eintrag (z. B. * * * * *) testen und nach erfolgreichem Lauf wieder löschen.
Warum läuft mein Cron-Job nicht?
Klassiker: PATH und Umgebungsvariablen sind unter cron meist minimal. Schreibe absolute Pfade (/usr/bin/python3, nicht python3), prüfe die Mailbox des Cron-Users auf Fehlermeldungen oder leite stdout/stderr in eine Logdatei um. Auch Berechtigungen und der korrekte User im crontab-Header werden gern übersehen.
Brauche ich heute überhaupt noch cron?
Für viele Szenarien gibt es Alternativen: systemd-Timer auf modernen Linux-Systemen, Kubernetes CronJobs in Containern, Lambda-Schedules in der Cloud. Aber cron ist überall vorhanden, gut verstanden und ohne extra Infrastruktur einsetzbar — wer eine einfache, regelmäßige Aufgabe auf einem Server braucht, fährt damit oft am pragmatischsten.
Was ist der Unterschied zwischen cron und at?
cron ist für wiederkehrende Aufgaben (»jeden Tag um 4 Uhr«), at ist für einmalige zukünftige Aufgaben (»am 24. Juni um 14 Uhr ein Skript starten«). at ist seltener installiert und weniger bekannt, aber praktisch für Wartungs-Fenster oder geplante Migrationen. Syntax: echo "/path/to/script.sh" | at 14:00 2026-06-24.
Wie testet man eine Cron-Expression, ohne sie laufen zu lassen?
Unser Cron-Expression-Generator zeigt für jede Expression die nächsten Ausführungs-Zeitpunkte. Alternativ kann man im Terminal croniter (Python-Bibliothek) oder node-cron nutzen, um zu prüfen, was eine Expression tatsächlich tut. Besonders bei komplexen Expressions wie 0 9 1-7 * 1 ist eine visuelle Vorschau Gold wert — diese Expression heißt nämlich »jeden Montag in der ersten Woche des Monats um 9 Uhr«, was wenig intuitiv ist.
Was ist mit Cloud-Schedulern wie AWS EventBridge oder Google Cloud Scheduler?
Beide nutzen Cron-Syntax (mit kleinen Variationen — AWS hat ein 6-Feld-Format mit Jahr), bieten aber zusätzlich Vorteile: hohe Verfügbarkeit, automatische Retries, Integration mit Lambda/Functions/Topics, Audit-Logs. Für Cloud-native Workloads sind sie die saubere Wahl. Kosten: typisch 1-5 EUR/Monat pro Job — günstiger als ein Server, der nur für cron läuft. Wer aber auf einem Server schon Workloads hat: cron lokal ist immer noch der einfachere Weg.
Kommentare
Die Kommentare werden von Disqus bereitgestellt. Bevor sie geladen werden, brauchen wir deine Einwilligung — Disqus ist ein Drittanbieter und setzt eigene Cookies.