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.

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.

Hinweis: Cron-Dialekte unterscheiden sich im Detail. Vor dem produktiven Einsatz solltest du die Doku deiner konkreten cron-Implementierung (oder deines Scheduling-Frameworks) konsultieren und kritische Jobs in einer Testumgebung verifizieren.