⚠️Stolperfallen

„Im Terminal geht’s, per cron nicht“ – fast immer steckt eine dieser Fallen dahinter. Jede ist hier nachgestellt, die Fakten sind mit Handbuchzitaten oder echten Testläufen belegt.

🌱1 · Minimale Umgebung (PATH)

Ein cron-Job startet nicht aus deiner Login-Shell. Es gibt keine .bashrc, kein .profile, kein conda/nvm – nur ein paar Variablen.
HOME=/home/demo LOGNAME=demo PATH=/usr/bin:/bin PWD=/home/demo SHELL=/bin/sh
📖 Testlauf: Debian 13, Paket cron 3.0pl1-197, crontab von Benutzer demo mit „* * * * * env | sort > /tmp/env-demo.txt“
HOME=/home/demo LOGNAME=demo USER=demo PATH=/usr/bin:/bin:/usr/sbin:/sbin SHELL=/bin/sh SHLVL=1
📖 derselbe Test mit cronie 1.7.2 (Fedora)
⚠️ Folgen
Programme in /usr/local/bin (selbst installiert, pip, npm -g) oder /usr/sbin (unter Debian) werden nicht gefunden → Exit-Code 127 „command not found“ – und das steht nur in der Mail, die niemand liest. Auch SHELL=/bin/sh heißt: kein Bash-Syntax wie [[ … ]].
✅ Lösungen
Volle Pfade im Befehl, oder oben in der crontab PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin setzen. Achtung: PATH=$HOME/bin:$PATH funktioniert NICHT – cron ersetzt keine Variablen (man 5 crontab: „not parsed for environmental substitutions“).

💯2 · Das %-Zeichen muss maskiert werden

/bin/sh -c bekommt
date +
stdin des Befehls
Y-⏎
m-⏎
d > /tmp/heute.txt

❌ Der Befehl wurde am ersten % abgeschnitten. Umleitungen und alles danach landen als Text auf stdin.

Percent-signs (%) in the command, unless escaped with backslash (\), will be changed into newline characters, and all data after the first % will be sent to the command as standard input.
📖 man 5 crontab (Debian, cronie, macOS)
$ ls /tmp env-demo.txt $ cat /tmp/pct.txt cat: /tmp/pct.txt: No such file or directory
📖 Testlauf Debian 13: Zeile „* * * * * env | sort > /tmp/env-demo.txt; date +%Y > /tmp/pct.txt 2>&1; echo $0 > /tmp/shell.txt“

Alles ab %Y wurde zu stdin von date – die beiden folgenden Befehle liefen nie. Richtig: date +\%Y oder ein Skript aufrufen.

🕑3 · Sommer-/Winterzeit: 02:30 fehlt oder kommt doppelt

In Europe/Berlin springt die Uhr am letzten Sonntag im März von 02:00 auf 03:00 und am letzten Sonntag im Oktober von 03:00 auf 02:00. Wie cron damit umgeht, hängt von der Implementierung ab.
30 2 * * *

🌸 Umstellung auf Sommerzeit · 29.03.2026

01:59 → 03:00 · UTC+01:00 → UTC+02:00 · um 01:00 UTC
WanduhrUTC00:0023:0001:0000:0003:0001:0004:0002:0005:0003:0006:0004:0007:0005:00Uhr springt vorReine WanduhrBusyBox, APScheduler✗ an diesem Tag kein Lauf in diesem FensterVixie cron / DebianSonderbehandlung aus cron(8)03:00systemd-Timergeprüft: systemd-analyze✗ an diesem Tag kein Lauf in diesem Fenster
normalnachgeholt direkt nach dem Sprungdoppelt (Uhrzeit gibt es zweimal)nur einmal trotz doppelter Uhrzeit
  • Reine Wanduhr: 0 Lauf/Läufe zwischen 00:00 und ca. 06:00 – keiner
  • Vixie cron / Debian: 1 Lauf/Läufe zwischen 00:00 und ca. 06:00 – 03:00 (UTC+02:00)
  • systemd-Timer: 0 Lauf/Läufe zwischen 00:00 und ca. 06:00 – keiner

🍂 Umstellung auf Winterzeit · 25.10.2026

02:59 → 02:00 · UTC+02:00 → UTC+01:00 · um 01:00 UTC
WanduhrUTC00:0022:0001:0023:0002:0000:0002:0001:0003:0002:0004:0003:0005:0004:00Uhr springt zurückReine WanduhrBusyBox, APScheduler02:3002:30Vixie cron / DebianSonderbehandlung aus cron(8)02:30systemd-Timergeprüft: systemd-analyze02:30
normalnachgeholt direkt nach dem Sprungdoppelt (Uhrzeit gibt es zweimal)nur einmal trotz doppelter Uhrzeit
  • Reine Wanduhr: 2 Lauf/Läufe zwischen 00:00 und ca. 06:00 – 02:30 (UTC+02:00), 02:30 (UTC+01:00)
  • Vixie cron / Debian: 1 Lauf/Läufe zwischen 00:00 und ca. 06:00 – 02:30 (UTC+02:00)
  • systemd-Timer: 1 Lauf/Läufe zwischen 00:00 und ca. 06:00 – 02:30 (UTC+02:00)
If the time has moved forwards, those jobs which would have run in the time that was skipped will be run soon after the change. Conversely, if the time has moved backwards by less than 3 hours, those jobs that fall into the repeated time will not be re-run. Only jobs that run at a particular time (not specified as @hourly, nor with '*' in the hour or minute specifier) are affected.
📖 man 8 cron, Debian 13 (gleichsinnig in cronies cron(8))
Note that this means that non-existent times, such as the "missing hours" during the daylight savings time conversion, will never match, causing jobs scheduled during the "missing times" not to be run. Similarly, times that occur more than once […] will cause matching jobs to be run twice.
📖 man 5 crontab, cronie 1.7.2 – widerspricht cron(8)
--base-time="2026-03-28 12:00 UTC" "*-*-* 02:30:00" Next elapse: Mon 2026-03-30 02:30:00 CEST --base-time="2026-10-24 12:00 UTC" Next elapse: Sun 2026-10-25 02:30:00 CEST Iteration #2: Mon 2026-10-26 02:30:00 CET
📖 systemd-analyze calendar, TZ=Europe/Berlin, systemd 257
✅ Empfehlung
Keine Jobs zwischen 02:00 und 03:00 (Europa) planen, den Server auf UTC betreiben oder den Zeitplan in UTC formulieren (systemd: OnCalendar=… UTC, Kubernetes: timeZone: Etc/UTC). GitHub Actions dokumentiert für Zeitpläne mit timezone: Läufe in der übersprungenen Stunde rücken auf die nächste gültige Zeit (02:30 → 03:00).

🔒4 · Überlappende Läufe

cron startet einen Job zur nächsten passenden Minute neu – egal, ob der vorige Lauf noch arbeitet (Ausnahme: BusyBox überspringt die Zeile, solange der letzte Lauf läuft).
*/5 * * * * …
Ohne Sperre · max. 3 gleichzeitig
+0+5+10+15+20+25+30+35+40+45+50+55+60
Mit flock -n /run/lock/job.lock · max. 1 gleichzeitig · 8 Starts übersprungen
+0+5+10+15+20+25+30+35+40+45+50+55+60✗✗✗✗✗✗✗✗
# überlappende Läufe verhindern: -n = nicht warten, sofort aufgeben
*/5 * * * *  /usr/bin/flock -n /run/lock/job.lock /usr/local/bin/job.sh

📜5 · Logging: wohin geht die Ausgabe?

Alles, was ein Job auf stdout oder stderr schreibt und nicht umgeleitet ist, sammelt cron ein und verschickt es als Mail – an MAILTO oder den Eigentümer der crontab. Die Reihenfolge der Umleitungen ist entscheidend.
stdout (1)
/var/log/backup.log (angehängt)
stderr (2)
/var/log/backup.log (angehängt)
  1. 1>>/var/log/backup.log: fd 1 → /var/log/backup.log
  2. 2>&1: fd 2 zeigt jetzt dorthin, wohin fd 1 gerade zeigt

✅ Nichts landet bei cron – es wird keine Mail erzeugt.

💡 MAILTO
MAILTO=admin@example.org schickt die Ausgabe dorthin, MAILTO="" schaltet Mails ab (Ausgabe verloren). Ohne Mailserver (kein sendmail/postfix) geht die Mail ins Leere.
💡 Wann lief der Job?
cron protokolliert Starts ins Syslog: journalctl -u cron (Debian) bzw. journalctl -u crond (cronie) oder grep CRON /var/log/syslog. Debian-cron protokolliert fehlgeschlagene Jobs (Exit ≠ 0) nur mit Loglevel -L 4.

🔑6 · Rechte und crontab-Befehle

crontab -e        # eigene crontab im Editor öffnen (prüft beim Speichern die Syntax)
crontab -l        # anzeigen
crontab -r        # LÖSCHEN – ohne Rückfrage! (liegt auf der Tastatur neben -e)
crontab -l > ~/crontab.bak   # Sicherung
crontab ~/crontab.bak        # aus Datei installieren
sudo crontab -u demo -l      # crontab eines anderen Benutzers (nur root)
⚠️ Wer darf?
Existiert /etc/cron.allow, dürfen nur die dort genannten Benutzer crontab benutzen; sonst sperrt /etc/cron.deny einzelne Benutzer. Die Dateien unter /var/spool/cron/crontabs/ nie direkt bearbeiten – immer über crontab.
💡 Als wer läuft der Job?
Benutzer-crontab: als dieser Benutzer, mit dessen Rechten und HOME als Arbeitsverzeichnis. Skripte brauchen das Ausführungsrecht (chmod +x) oder einen Aufruf wie /bin/sh skript.sh.

🗂️7 · /etc/crontab vs. /etc/cron.d vs. Benutzer-crontab

OrtBenutzerfeldbearbeiten mitBeispielzeile
crontab -e (Benutzer)neincrontab -e30 4 * * 1 /home/demo/bin/backup.sh
/etc/crontabjaEditor (root), kein Neuladen nötig25 6 * * * root test -x /usr/sbin/anacron || …
/etc/cron.d/<name>jaPaket oder root legt Datei ab30 7-23 * * * root … (aus /etc/cron.d/anacron)
/etc/cron.daily/ usw.– (Skripte)ausführbare Skripte, via run-parts/etc/cron.daily/logrotate
[Files in /etc/cron.d] must conform to the same naming convention as used by run-parts(8): they must consist solely of upper- and lower-case letters, digits, underscores, and hyphens.
📖 man 8 cron, Debian 13
⚠️ Die unsichtbare Datei
/etc/cron.d/backup.sh oder /etc/cron.d/job.conf ignoriert Debian-cron wegen des Punkts im Namen – kommentarlos. Und umgekehrt: Wer eine Zeile mit Benutzerfeld in crontab -e einträgt, lässt cron den Benutzernamen als Befehl ausführen. Der Debugger meldet beides.

💤8 · anacron: für Rechner, die nachts aus sind

cron verpasst Jobs, wenn der Rechner zur geplanten Zeit ausgeschaltet ist. anacron zählt stattdessen Tage: Ist ein Job seit n Tagen nicht gelaufen, startet er ihn nach einer Verzögerung nach.
SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin HOME=/root LOGNAME=root # These replace cron's entries 1 5 cron.daily run-parts --report /etc/cron.daily 7 10 cron.weekly run-parts --report /etc/cron.weekly @monthly 15 cron.monthly run-parts --report /etc/cron.monthly @yearly 20 cron.yearly run-parts --report /etc/cron.yearly
📖 /etc/anacrontab, Debian 13 (Paket anacron)
SpalteBeispielBedeutung
Periode1 / 7 / @monthlyalle n Tage (oder @monthly, @yearly)
Verzögerung5Minuten Wartezeit nach dem Start von anacron
Kennungcron.dailyName der Zeitstempel-Datei (unter /var/spool/anacron/)
Befehlrun-parts …was ausgeführt wird

Debians /etc/crontab ruft cron.daily/weekly/monthly nur auf, wenn anacron fehlt: test -x /usr/sbin/anacron || …. anacron kennt keine Uhrzeiten und nur ganze Tage.