# Cron- & Zeitzonen-Prüfstand – Zeitpläne auf Deutsch verstehen, Sommerzeit-Fallen prüfen und exportieren

> **Autor:** Lukas Kopischke (Lead Software-Engineer, Düsseldorf)
> **Kanonische URL:** https://lukiluki.me/garden/cron-planner
> **Markdown-Endpunkt:** https://lukiluki.me/garden/cron-planner.md
> **Kategorie:** Workflows & Testing | **Tags:** POSIX-Cron, IANA-Timezones, Systemd-Timer, GitHub-Actions, Offline-PWA
> **Veröffentlicht:** 2026-10-27 | **Aktualisiert:** 2026-10-27
> **Zusammenfassung:** Interaktiver Cron-Prüfstand im Browser: 5-Felder-Syntax auf Deutsch erklären, UTC- und Sommerzeit-Fallen erkennen sowie Systemd- und GitHub-Timer erzeugen.

---
Ein nächtliches Datenbank-Backup steht auf `30 2 * * *` – und am letzten Sonntag im März existiert kein einziges Backup-Archiv, während das Skript Ende Oktober zweimal hintereinander anläuft. Oder du trägst `0 9 13 * 5` ein, um einen Prüfjob nur an einem „Freitag, dem 13.“ zu starten, wunderst dich aber, warum er plötzlich jeden einzelnen Freitag im Jahr feuert.

Ein klassischer POSIX-Cron-Daemon (Standardisierter Hintergrunddienst unter Unix/Linux (IEEE Std 1003.1), der jede Minute die Systemuhr gegen fünf Zeitfelder prüft und fällige Kommandos startet.) vergleicht jede volle Minute fünf Felder (`Minute`, `Stunde`, `Monatstag`, `Monat`, `Wochentag`) mit der lokalen Systemuhr. Weil Monatstag und Wochentag laut Spezifikation per logischem **ODER** verknüpft sind und Cloud-Plattformen wie GitHub Actions strikt in `UTC` rechnen, entstehen ohne Zeitzonen-Prüfung stille Ausfälle. Zudem holt klassisches Cron verpasste Läufe nach einem Server-Neustart nicht nach – hierfür sind `systemd`-Timer mit `Persistent=true` die robustere Wahl.

> **Merksatz:**
> Prüfe jeden Cron-Ausdruck immer gemeinsam mit seiner Ziel-Zeitzone: Cloud-Runner rechnen fast immer in UTC, lokale Linux-Server stolpern zwischen 02:00 und 02:59 Uhr über die Zeitumstellung, und gesetzter Monatstag plus Wochentag wirken als ODER statt UND.

## Wie wertet Cron seine fünf Felder aus – und wo lauern die drei teuersten Denkfehler?

Jede Zeile einer Crontab beginnt mit fünf durch Leerzeichen getrennten Zeitfeldern:

1. **Minute (`0–59`):** Zu welcher Minute der Stunde soll der Befehl starten? (`*/15` entspricht `0, 15, 30, 45`).
2. **Stunde (`0–23`):** In welcher Stunde des Tages (24-Stunden-Zählung)?
3. **Tag im Monat (`1–31`):** An welchem Kalendertag?
4. **Monat (`1–12` oder `JAN–DEC`):** In welchem Monat?
5. **Wochentag (`0–6` bzw. `7`):** An welchem Wochentag (`0` und `7` stehen beide für Sonntag)?

In der Praxis sorgen drei Eigenheiten dieses Modells regelmäßig für Produktionsfehler:

### 1. Die POSIX-ODER-Regel zwischen Monatstag und Wochentag

Solange eines der beiden Tagesfelder auf `*` steht, verhält sich Cron erwartungsgemäß (`15 8 * * 1-5` läuft montags bis freitags um 08:15 Uhr). Sobald jedoch **beide** Felder vom Sternchen abweichen – etwa `0 9 13 * 5` –, bildet der Standard die **Vereinigungsmenge (ODER)**: Der Job startet am 13. jedes Monats **sowie** an jedem Freitag. Willst du wirklich eine UND-Bedingung (z. B. *„erster Montag im Monat“* für `0 9 1-7 * 1`), setzt du in der Crontab nur das Datumsfenster `1-7` und prüfst den Wochentag im Aufrufbefehl.

### 2. Sommer- und Winterzeit (`02:00` bis `02:59` Uhr)

Läuft dein Server in einer Zeitzone mit Sommerzeitumstellung wie IANA Europe/Berlin (Eindeutige Zeitzonen-Kennung der Internet Assigned Numbers Authority, die für Mitteleuropa sowohl Normalzeit (MEZ, UTC+1) als auch Sommerzeit (MESZ, UTC+2) inklusive historischer Umstellungsregeln definiert.), springt die Uhr am letzten März-Sonntag um `02:00` direkt auf `03:00` vor: Ein auf `30 2 * * *` geplanter Job fällt an diesem Tag ersatzlos aus. Am letzten Oktober-Sonntag springt die Uhr von `03:00` auf `02:00` zurück – naive Cron-Implementierungen ohne Sonderbehandlung führen Jobs in dieser Stunde doppelt aus.

### 3. Überlappende Prozesse ohne Sperrdatei (`flock`)

Cron startet Kommandos blind nach Uhrzeit und prüft **nicht**, ob der vorherige Lauf desselben Skripts noch arbeitet. Hängt ein `*/5 * * * *`-Import wegen einer langsamen Gegenstelle für 20 Minuten, laufen nach einer Viertelstunde vier identische Prozesse parallel und blockieren Datenbank oder Arbeitsspeicher. Unter Linux schützt das Dienstprogramm `flock` zuverlässig vor solchen Kaskaden:

```bash
# Sichere Produktions-Crontab: explizite Zeitzone + nicht-blockierende Sperrdatei (flock -n)
CRON_TZ=Europe/Berlin

# Läuft täglich um 03:15 Uhr (außerhalb des 02:xx-Zeitumstellungsfensters!)
15 3 * * * /usr/bin/flock -n /run/lock/nightly-backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Echte UND-Verknüpfung für „nur am ersten Montag des Monats um 08:00 Uhr“:
# Cron prüft die Tage 1..7, test(1) prüft zusätzlich Wochentag 1 (Montag)
0 8 1-7 * * [ "$(date +\%u)" = "1" ] && /usr/local/bin/monthly-report.sh
```

> Beachte das maskierte Prozentzeichen `\%` im `date`-Aufruf: In einer Crontab interpretiert der Daemon jedes unmaskierte `%` als Zeilenumbruch für die Standardeingabe (`stdin`) – einer der häufigsten Syntaxfehler in Shell-Einzeilern.

## Welcher Zeitgeber passt zu welchem Einsatz – Crontab, Systemd-Timer oder GitHub Actions?

| Plattform / Dienst | Standard-Zeitzone | Verpasste Läufe nach Ausfall? | Überlappungsschutz | Typischer Einsatz & wichtige Grenze |
| :--- | :--- | :--- | :--- | :--- |
| **Klassische Linux `crontab`** | Lokale Server-Zeitzone | **Nein** (Ausfallzeit = Job entfällt) | Manuell über `flock -n` nötig | Schnell eingerichtet auf jedem Unix-System; kein eingebautes Logging pro Lauf |
| **`systemd.timer` (`OnCalendar=`)** | Lokale Server-Zeitzone (oder `UTC`) | **Ja**, mit `Persistent=true` sofort nach Reboot | **Automatisch** (Service-Unit läuft nicht doppelt) | Erste Wahl auf modernen Linux-Servern und Laptops; etwas mehr Konfigurationsdateien (`.timer` + `.service`) |
| **GitHub Actions (`on.schedule`)** | **Immer zwingend `UTC`** | Nein | Über `concurrency:` konfigurierbar | Kein eigener Server nötig; bei hoher Plattformlast sind Verzögerungen von **5 bis 20 Minuten** normal, Mindestintervall beträgt `5 Minuten` |

  #### Empfohlen (Do): Robuste Zeitplanung im Betrieb
* Nächtliche Wartungsfenster in `Europe/Berlin` auf **01:xx** oder **03:xx / 04:xx Uhr** legen, um die 02:00-Uhr-Umstellungsstunde komplett zu meiden
    * Wiederkehrende Skripte immer idempotent schreiben und mit `flock -n` oder `systemd`-Units gegen parallele Doppelstarts verriegeln
    * Bei GitHub Actions zusätzlich `workflow_dispatch:` aktivieren, damit sich der Job im Ernstfall jederzeit per Klick ohne Wartezeit starten lässt
  #### Vermeiden (Don't): Typische Produktionsfallen
* Keine minutengenauen Echtzeit-Anforderungen auf GitHub Actions `schedule` stützen – der Queue-Verzug auf öffentlichen Runnern schwankt spürbar
    * Keine ungeschützten `%`-Zeichen (wie `date +%F`) ohne Backslash `\%` direkt in eine Crontab-Zeile schreiben
    * Nicht alle Jobs einer Server-Flotte exakt auf `0 0 * * *` (Mitternacht Punkt null) bündeln – das erzeugt künstliche Lastspitzen („Thundering Herd“)

> **Warum krumme Minuten wie 17 3 * * * APIs entlasten**
> Millionen automatisierter Systeme weltweit feuern exakt zur vollen Stunde (`0 * * * *`) oder um Mitternacht (`0 0 * * *`). Wählst du für unkritische Abfragen bewusst eine krumme Minute wie `17 3 * * *` oder ergänzt bei `systemd` `RandomizedDelaySec=45s`, umgehst du fremde Rate-Limits und überlastete Gegenstellen.

## Prüfpunkte vor dem Aktivieren eines neuen Cronjobs

- [ ] **Zeitzone des Zielsystems verifizieren:** Prüfe per `timedatectl` (Linux) bzw. in der Plattform-Doku, ob der Scheduler in `Europe/Berlin` oder `UTC` rechnet.
- [ ] **März- und Oktober-Sprung gegenprüfen:** Stelle im obigen Prüfstand den Zeit-Anker testweise auf *März-Sommerzeit* und *Oktober-Winterzeit*, um Ausfälle oder Doppelläufe auszuschließen.
- [ ] **Umgebungsvariablen (`PATH`) explizit setzen:** Cron startet mit minimalem `PATH=/usr/bin:/bin` – verwende absolute Binärpfade wie `/usr/local/bin/node`, damit Skripte nicht trotz erfolgreichem Handtest im Terminal scheitern.
- [ ] **Fehlerausgabe sichtbar machen:** Leite `stderr` (`2>&1`) in ein rotiertes Logfile um oder prüfe den Status bei `systemd` über `journalctl -u mein-job.service`.

## Quellen und nächste Schritte

* **The Open Group Base Specifications (IEEE Std 1003.1 / POSIX.1-2024)** – *`crontab` Utility & Day-of-Month / Day-of-Week Specification*: [pubs.opengroup.org/onlinepubs/9799919799/utilities/crontab.html](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/crontab.html)
* **freedesktop.org systemd Manual** – *`systemd.timer` & `systemd.time` Calendar Events*: [www.freedesktop.org/software/systemd/man/latest/systemd.timer.html](https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html)
* **GitHub Docs** – *Events that trigger workflows: `schedule` (UTC & Queue Latency Notes)*: [docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule)
* **Weiterführend im Digital Garden:** Lies in [Git Workflows](/garden/git-workflows), wie automatisierte Release- und Prüf-Pipelines sauber zusammenspielen, oder prüfe geänderte Server-Konfigurationen vor dem Einspielen lokal im [Diff-Checker & Smart-Formatter](/garden/diff-checker).

---
*Quelle: [Lukas Kopischke – Digital Garden](https://lukiluki.me/garden/cron-planner) | Vollständiger Index: [llms.txt](https://lukiluki.me/llms.txt)*
