Einen Cronjob erstellen heißt: Du sagst Linux in einer einzigen Zeile, welcher Befehl wann laufen soll. Jeden Morgen um 4 Uhr ein Backup, alle fünf Minuten ein Gesundheitscheck, jeden Montag ein Bericht. Dafür öffnest du mit crontab -e deine persönliche Zeitplan-Tabelle, schreibst fünf Zeitfelder plus den Befehl hinein und speicherst. Fertig. Mehr braucht es für den ersten Cronjob wirklich nicht.
Schwierig wird es danach. Denn Cron ist ein sehr altes, sehr schweigsames Werkzeug: Wenn ein Job nicht läuft, sagt es dir das selten. Auf unserem eigenen Server laufen 41 Cronjobs in der root-Crontab plus acht Dateien in /etc/cron.d/. Für diesen Artikel haben wir am 6. Oktober 2026 Test-Jobs gebaut, die absichtlich in die bekannten Fallen laufen, und unsere eigene Crontab durchleuchtet. Drei Befunde vorweg:
1. Ein Job, der länger läuft als sein Intervall, wird von Cron trotzdem jede Minute neu gestartet. In unserem Test liefen nach drei Minuten drei Kopien gleichzeitig. Cron kennt keine Überlappungssperre.
2. Eine Datei in
/etc/cron.d/ohne abschließenden Zeilenumbruch wird komplett ignoriert, und eine Datei mit einem Punkt im Namen ebenfalls. Im ersten Fall steht immerhin eine Zeile im Log, im zweiten gar nichts.3. Cron schickt jede Ausgabe per Mail an den Besitzer. Bei uns landen diese Mails seit Wochen bei einer Adresse, die es nicht gibt: 273 Rückläufer in einem Monat, die niemand gelesen hat.

Was ist ein Cronjob?
Cron ist ein Hintergrunddienst (ein „Daemon”), der auf praktisch jedem Linux- und Unix-System läuft. Er wacht jede Minute auf, schaut in seine Tabellen und startet alles, dessen Zeit gekommen ist. Ein Cronjob ist ein einzelner Eintrag in so einer Tabelle, und die Tabelle selbst heißt Crontab (von cron table). Der Name kommt vom griechischen chronos, Zeit.
Auf Ubuntu und Debian heißt das Paket schlicht cron, auf Fedora, Rocky Linux und Arch meist cronie. Die Bedienung ist überall gleich. Unser Testsystem: Ubuntu 24.04 mit cron 3.0pl1-184ubuntu2. Ob der Dienst läuft, siehst du so:
systemctl status cron # Ubuntu, Debian
systemctl status crond # Fedora, Rocky, Arch (cronie)
Bei uns lief er seit drei Wochen ohne Unterbrechung und hatte in den letzten 24 Stunden 2.928 Befehle gestartet. Das klingt viel, ist aber typisch für einen Server, auf dem ein paar Prüfskripte alle drei oder fünf Minuten laufen.
Wenn du deinen Server gerade erst aufsetzt, steht in unserem Artikel zum Linux-Server einrichten, was davor kommt. Für die Grundlagen der Kommandozeile auf einem entfernten Rechner hilft Was ist SSH.
Kurze deutsche Einführung vom Hoster MainHoster. Das Video ist älter und wirbt nebenbei für den eigenen Anbieter, die gezeigten Befehle gelten aber unverändert.
Cronjob erstellen: die Kurzanleitung
Wenn du nur schnell einen Cronjob erstellen willst, reichen diese vier Schritte:
1. Crontab öffnen:
crontab -e
Beim ersten Aufruf fragt Ubuntu nach einem Editor. Für Einsteiger ist nano die angenehmste Wahl (Speichern mit Strg+O, Beenden mit Strg+X). Den Editor kannst du später mit select-editor wechseln.
2. Eine Zeile ans Ende schreiben, zum Beispiel ein tägliches Skript um 4:30 Uhr:
30 4 * * * /home/fabian/bin/backup.sh >> /home/fabian/logs/backup.log 2>&1
3. Speichern und schließen. Cron meldet crontab: installing new crontab. Ein Neustart des Dienstes ist nicht nötig, Cron liest geänderte Tabellen von selbst ein.
4. Prüfen, ob der Eintrag angekommen ist:
crontab -l
Das war es. Die restlichen Abschnitte erklären, was die fünf Sternchen bedeuten, warum das >> ... 2>&1 am Ende kein Schmuck ist, und warum dein Skript im Terminal läuft, in Cron aber nicht.
Die wichtigsten crontab-Befehle
| Befehl | Was er tut |
|---|---|
crontab -e | eigene Crontab bearbeiten (legt sie beim ersten Mal an) |
crontab -l | eigene Crontab anzeigen |
crontab -r | eigene Crontab komplett löschen, ohne Rückfrage |
crontab -i -r | dasselbe, aber mit Rückfrage |
sudo crontab -u alice -e | Crontab eines anderen Benutzers bearbeiten |
crontab datei.txt | Crontab durch den Inhalt einer Datei ersetzen |
Ein Hinweis zu crontab -r: Das r liegt auf der Tastatur direkt neben dem e. Ein Tippfehler, und alle Jobs sind weg. Wir machen deshalb vor größeren Änderungen eine Kopie:
crontab -l > ~/crontab-backup-$(date +%F).txt
Wiederherstellen geht dann mit crontab ~/crontab-backup-2026-10-06.txt.
Die crontab-Syntax: fünf Felder, ein Befehl
Jede Zeile in einer Crontab besteht aus fünf Zeitfeldern, gefolgt vom Befehl:
┌───────────── Minute (0–59)
│ ┌─────────── Stunde (0–23)
│ │ ┌───────── Tag im Monat (1–31)
│ │ │ ┌─────── Monat (1–12 oder jan–dec)
│ │ │ │ ┌───── Wochentag (0–7, 0 und 7 = Sonntag, oder sun–sat)
│ │ │ │ │
* * * * * befehl
Ein * bedeutet „jeder Wert”. Die Zeile * * * * * befehl läuft also jede Minute, an jedem Tag, in jedem Monat.

Die Sonderzeichen
| Zeichen | Bedeutung | Beispiel | Läuft |
|---|---|---|---|
* | jeder Wert | * * * * * | jede Minute |
, | Liste | 0 8,12,18 * * * | um 8, 12 und 18 Uhr |
- | Bereich | 0 9-17 * * 1-5 | stündlich 9 bis 17 Uhr, Mo–Fr |
/ | Schrittweite | */15 * * * * | alle 15 Minuten |
| Kombination | Bereich mit Schritt | 0 8-20/4 * * * | 8, 12, 16, 20 Uhr |
Cronjob-Beispiele zum Kopieren
| Zeitplan | Crontab-Zeile |
|---|---|
| jede Minute | * * * * * |
| alle 5 Minuten | */5 * * * * |
| jede Stunde zur vollen Stunde | 0 * * * * |
| jeden Tag um 3:00 Uhr | 0 3 * * * |
| jeden Tag um 3:00 und 15:00 Uhr | 0 3,15 * * * |
| werktags um 7:30 Uhr | 30 7 * * 1-5 |
| jeden Sonntag um 4:47 Uhr | 47 4 * * 0 |
| am 1. jedes Monats um Mitternacht | 0 0 1 * * |
| alle 2 Stunden um :45 | 45 */2 * * * |
| einmal im Jahr am 1. Januar | 0 0 1 1 * |
Ein Tipp aus der Praxis: Nimm keine runden Zeiten, wenn es nicht sein muss. Um Punkt 0:00 Uhr und zu jeder vollen Stunde startet auf einem Server ohnehin schon viel. Bei uns liefen um 4:00:01 Uhr am Testmorgen zehn Jobs gleichzeitig los. Unsere eigenen Wartungsjobs liegen deshalb bewusst auf krummen Minuten wie :23, :41 oder :47. Wer seine Ausdrücke vor dem Speichern prüfen will, findet bei crontab.guru eine Übersetzung in normale Sprache.
Die Kurzschreibweisen
Statt fünf Feldern kannst du auch eines dieser Schlüsselwörter schreiben:
| Schlüsselwort | entspricht |
|---|---|
@reboot | einmal beim Start des Cron-Dienstes |
@hourly | 0 * * * * |
@daily / @midnight | 0 0 * * * |
@weekly | 0 0 * * 0 |
@monthly | 0 0 1 * * |
@yearly / @annually | 0 0 1 1 * |
@reboot nutzen wir zweimal, etwa für einen Schutz-Check kurz nach dem Hochfahren. Wichtig dabei: @reboot heißt „wenn Cron startet”, nicht „wenn das Netzwerk bereit ist”. Darum steht bei uns ein sleep 30 && davor. Für alles, was zuverlässig nach dem Netzwerk oder einer Datenbank starten soll, ist ein systemd-Service mit After= die bessere Wahl.
Die Falle mit Tag und Wochentag
Diese Regel überrascht fast jeden: Wenn sowohl der Tag im Monat als auch der Wochentag gesetzt sind (also beide kein *), dann verknüpft Cron sie mit ODER, nicht mit UND.
Wir haben das live getestet. Die Zeile
* * 1 * 2 befehl
sieht aus wie „am 1. des Monats, wenn es ein Dienstag ist”. Unser Testtag war Dienstag, der 6. Oktober, also ganz sicher kein Monatserster. Ergebnis im Log:
07:07:01 dom1-or-tuesday
Der Job lief. Er läuft an jedem 1. des Monats und zusätzlich an jedem Dienstag. Die Vergleichszeile * * 1 * * (nur der 1.) lief am selben Tag erwartungsgemäß nicht.
Wer wirklich „erster Montag im Monat” will, schreibt den Wochentag als Bedingung in den Befehl:
0 9 1-7 * * [ "$(date +\%u)" = 1 ] && /pfad/zum/skript.sh
Also: an den Tagen 1 bis 7 starten, und nur weitermachen, wenn heute Montag ist (%u = 1). Das \% kommt gleich noch dran.
Wo Cronjobs liegen: Benutzer-Crontab und Systemdateien
Es gibt nicht nur eine Crontab. Auf einem typischen Ubuntu-Server findest du Cronjobs an diesen Orten:
| Ort | Bearbeiten mit | Benutzerfeld? | Typischer Zweck |
|---|---|---|---|
/var/spool/cron/crontabs/NAME | crontab -e | nein | persönliche Jobs eines Benutzers |
/etc/crontab | Editor (als root) | ja | systemweite Haupttabelle |
/etc/cron.d/* | Editor (als root) | ja | Jobs, die Pakete mitbringen |
/etc/cron.hourly/, .daily/, .weekly/, .monthly/ | Skript hineinlegen | – | ganze Skripte ohne eigene Zeitangabe |
Die Dateien unter /var/spool/cron/crontabs/ niemals direkt editieren, sondern immer über crontab -e. Nur so prüft crontab die Syntax und Cron bekommt die Änderung zuverlässig mit.
Der wichtigste Unterschied: In /etc/crontab und /etc/cron.d/ steht zwischen Zeitangabe und Befehl ein Benutzername:
# /etc/cron.d/beispiel
30 4 * * * root /usr/local/bin/backup.sh
In einer Benutzer-Crontab (crontab -e) gibt es dieses Feld nicht. Wer eine Zeile von dort nach /etc/cron.d/ kopiert und den Benutzer vergisst, bekommt einen Job, der versucht, als Benutzer /usr/local/bin/backup.sh zu laufen, und dann gar nichts tut.
Bei uns liegen in /etc/cron.d/ acht Dateien, die meisten aus Paketen: Zertifikats-Erneuerung (certbot), PHP-Sitzungsaufräumen, Systemstatistik (sysstat), Webmail-Aufräumen. Die Tagesordner /etc/cron.daily/ enthalten unter anderem logrotate, apt-compat und man-db. Wann die laufen, steht in /etc/crontab, bei Ubuntu täglich um 6:25 Uhr, sofern kein anacron installiert ist.
Zwei stille Fallen in /etc/cron.d/
Beide haben wir am Testmorgen absichtlich ausgelöst.
Erstens: Die letzte Zeile braucht einen Zeilenumbruch. Wir haben eine Datei geschrieben, deren einzige Zeile ohne abschließendes Enter endete. Cron hat sie nicht ausgeführt. Im Journal stand:
cron[514899]: (*system*gm-cron-nonl) ERROR (Missing newline before EOF,
this crontab file will be ignored)
Die ganze Datei wird ignoriert, nicht nur die letzte Zeile. Das passiert typischerweise, wenn man Dateien per Skript mit printf oder echo -n erzeugt. crontab -e schützt dich davor, ein normaler Editor meist auch.
Zweitens: Kein Punkt im Dateinamen. Cron auf Debian und Ubuntu ignoriert in /etc/cron.d/ alle Dateien, deren Name einen Punkt enthält (das soll Paket-Backups wie .dpkg-old ausschließen). Unsere Datei gm-cron.dot mit einem fehlerfreien Job lief kein einziges Mal, und diesmal stand keine Zeile im Log. Eine Datei namens backup.cron oder meinjob.sh in /etc/cron.d/ ist damit tot, ohne dass du es merkst. Erlaubt sind Buchstaben, Ziffern, Bindestrich und Unterstrich.
Warum der Cronjob im Terminal läuft, aber nicht in Cron
Das ist mit Abstand die häufigste Frage zu Cron, und die Antwort ist fast immer dieselbe: Cron startet deinen Befehl in einer fast leeren Umgebung. Kein .bashrc, kein .profile, keine Aliase, kein Node-Version-Manager, ein kurzer PATH.
Wir haben uns angesehen, was ein Cronjob bei uns tatsächlich zu sehen bekommt. Der Testjob env > /tmp/gmcron/env.txt lieferte genau zehn Variablen:
HOME=/root
LOGNAME=root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:...
SHELL=/bin/sh
LANG=en_US.UTF-8
PWD=/root
DISPLAY=:99
ANDROID_HOME=...
Zum Vergleich hatte die interaktive Shell auf demselben Server sechs zusätzliche PATH-Einträge: ~/.local/bin, ~/.npm-global/bin, ~/.bun/bin, die Shims von asdf und volta, ~/.nvm/current/bin. Alles, was du per npm install -g, pip install --user oder einen Versionsmanager installiert hast, ist in Cron unsichtbar.
Dass bei uns überhaupt mehr als die Debian-Grundausstattung im PATH steht, liegt an /etc/environment: Cron liest diese Datei auf Debian und Ubuntu über PAM mit ein. Das ist praktisch, aber eine Eigenheit dieser Distributionen. Auf anderen Systemen hast du womöglich nur /usr/bin:/bin.

Weitere Unterschiede, die regelmäßig Jobs kaputt machen:
- Die Shell ist
/bin/sh, nicht Bash. Auf Ubuntu ist dasdash. Bash-Eigenheiten wie[[ ... ]],source,{1..5}oder$RANDOMfunktionieren dort nicht oder anders. EntwederSHELL=/bin/bashoben in die Crontab schreiben oder (besser) den Befehl in ein Skript mit#!/bin/bashpacken. - Das Arbeitsverzeichnis ist das Home-Verzeichnis. Relative Pfade wie
./daten/export.csvlanden woanders als gedacht. Im Skript zuerstcd /pfad/zum/projekt || exit 1. - Keine Umgebungsvariablen aus
.bashrc. API-Schlüssel,NODE_ENV, Proxy-Einstellungen: Was du dort exportierst, sieht Cron nicht. - Kein Terminal. Programme, die interaktiv fragen oder Farben ausgeben wollen, verhalten sich anders oder brechen ab.
So machst du einen Cronjob robust:
# Oben in der Crontab (gilt für alle Zeilen darunter)
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""
# Immer absolute Pfade, und ein eigenes Skript statt langer Einzeiler
30 4 * * * /home/fabian/bin/backup.sh >> /home/fabian/logs/backup.log 2>&1
Und im Skript selbst:
#!/bin/bash
set -euo pipefail
cd /home/fabian/projekt
export NODE_ENV=production
/home/fabian/.nvm/versions/node/v22.20.0/bin/node export.js
Zum Testen, ob ein Skript die karge Umgebung übersteht, gibt es einen einfachen Trick: Starte es so, wie Cron es täte.
env -i HOME="$HOME" LOGNAME="$USER" PATH=/usr/bin:/bin SHELL=/bin/sh \
/bin/sh -c '/home/fabian/bin/backup.sh'
Läuft es hier, läuft es auch in Cron.
Das Prozentzeichen: der Fehler, den man nicht sieht
Ein Zeichen hat in einer Crontab eine Sonderbedeutung, die fast niemand kennt: Das % wird in einen Zeilenumbruch verwandelt. Alles nach dem ersten % geht dem Befehl als Eingabe zu, nicht als Teil der Kommandozeile.
Wir haben zwei fast identische Testzeilen laufen lassen:
* * * * * date +%H:%M:%S.%N >> /tmp/gmcron/start.txt
* * * * * date +\%H:\%M:\%S.\%N >> /tmp/gmcron/start-escaped.txt 2>&1
Die zweite Datei füllte sich jede Minute mit Uhrzeiten. Die erste Datei wurde nie angelegt. Im Cron-Log stand als Befehl nur:
(root) CMD (date +)
Cron hatte die Zeile am ersten % abgeschnitten. Die Umleitung >> /tmp/gmcron/start.txt stand nach dem % und wurde damit zur Eingabe für date, die date ignoriert. Es gab also keine Datei, keine Fehlermeldung in einer Datei, und der Job galt für Cron als ausgeführt.
Die Lösung: In Crontab-Zeilen jedes % als \% schreiben. Oder, wieder besser: das date-Kommando in ein Skript auslagern. In Skripten gilt die Sonderregel nicht.
Ausgabe und Logs: wohin der Output geht
Cron fängt alles ab, was dein Befehl auf die Standardausgabe oder die Fehlerausgabe schreibt, und verschickt es per E-Mail an den Besitzer der Crontab (oder an die Adresse in MAILTO). Das ist ein Erbe aus Zeiten, in denen jeder Unix-Rechner lokale Post hatte.
Auf einem heutigen Server gibt es drei mögliche Ausgänge:
- Kein Mailprogramm installiert: Cron verwirft die Ausgabe und schreibt höchstens
(CRON) info (No MTA installed, discarding output)ins Log. - Mailprogramm installiert und korrekt eingerichtet: Die Mail kommt an. Das ist der gewünschte Fall, aber selten.
- Mailprogramm installiert, aber für etwas anderes eingerichtet: Die Mail geht irgendwohin. Und das war unser Fall.

Unser Fund: 273 Mails ins Leere
Auf unserem Server läuft ein Mailserver (Postfix) für ein anderes Projekt. Ein Systemjob in /etc/cron.d/, der dreimal am Tag jeweils drei kurze Befehle startet, erzeugt dabei Ausgabe. Cron verpackt sie brav als Mail an root. Postfix ergänzt root um seine Standard-Domain, und die gehört zu einem Altprojekt, in dessen Postfach es keinen Benutzer root gibt. Ergebnis im Mail-Log:
status=bounced (... said: 550 5.1.1 <root@...> User doesn't exist)
Gezählt in den Mail-Logs vom 6. September bis 6. Oktober: 273 Rückläufer von orig_to=<root>, verlässlich neun pro Tag (um 4, 12 und 20 Uhr, jeweils drei Stück). Jeder Rückläufer erzeugt zusätzlich eine Fehlermeldung, die ebenfalls nicht zugestellt werden kann. Niemand hat diese Ausgabe je gelesen. Hätte der Job einen echten Fehler gemeldet, wäre er an genau dieselbe Stelle verschwunden.
Wir haben daran bewusst nichts geändert, während wir den Artikel geschrieben haben; das ist eine eigene Aufräum-Entscheidung. Aber die Lehre gilt für jeden: Wenn du nicht ganz sicher weißt, wo die Cron-Mails deines Servers hingehen, verlass dich nicht darauf.
Die bessere Lösung: Ausgabe selbst umleiten
Von unseren 41 Jobs in der root-Crontab leiten 34 ihre Ausgabe selbst um: 21 in eine eigene Logdatei, 13 nach /dev/null. Sieben schreiben nirgendwohin und verlassen sich damit auf den Mailweg. Die Muster:
# Alles in eine Logdatei anhängen (Standard für wichtige Jobs)
30 4 * * * /pfad/backup.sh >> /var/log/backup.log 2>&1
# Nur Fehler behalten, normale Ausgabe wegwerfen
*/5 * * * * /pfad/check.sh > /dev/null 2>> /var/log/check-fehler.log
# Alles wegwerfen (nur, wenn das Skript selbst protokolliert!)
*/3 * * * * /pfad/ping.sh > /dev/null 2>&1
# Ins Systemjournal schreiben, mit eigenem Namen
0 3 * * * /pfad/aufraeumen.sh 2>&1 | logger -t aufraeumen
Die Reihenfolge >> datei 2>&1 ist wichtig: erst die normale Ausgabe in die Datei, dann die Fehlerausgabe dorthin, wo die normale schon hingeht. Andersherum (2>&1 >> datei) landen die Fehler weiter in der Mail.
Die logger-Variante gefällt uns am besten, weil die Ausgabe dann zusammen mit allem anderen im Journal steht und mit journalctl -t aufraeumen lesbar ist, inklusive automatischer Rotation.
Logdateien, in die Cron jahrelang anhängt, wachsen übrigens unbegrenzt. Entweder per logrotate einbinden oder im Skript selbst kürzen.
Hat mein Cronjob überhaupt ausgeführt?
Cron protokolliert jeden Start. Auf Ubuntu siehst du das im Journal:
journalctl -u cron --since "1 hour ago"
grep CRON /var/log/syslog | tail -20 # falls rsyslog läuft
Eine Zeile sieht so aus:
Oct 06 07:04:01 Nyx CRON[1563376]: (root) CMD (env > /tmp/gmcron/env.txt 2>&1)
Wichtig: Diese Zeile beweist nur, dass Cron den Befehl gestartet hat. Ob er erfolgreich war, steht dort nicht. Unser %-Testjob tauchte jede Minute brav als CMD (date +) auf und hat trotzdem nie etwas getan. Erfolg prüfst du nur über die Ausgabe des Jobs selbst, also über deine Logdatei oder einen Exit-Code, den du irgendwo festhältst.
Überlappung verhindern: flock
Cron startet einen Job zur geplanten Zeit, egal ob die vorige Ausführung noch läuft. Für Jobs, die normalerweise schnell sind, aber manchmal hängen (Netzwerk langsam, Datenbank gesperrt, Backup größer als gedacht), ist das gefährlich. Zwei Backups, die gleichzeitig in dieselbe Datei schreiben, sind schlimmer als keins.
Unser Test: ein Job, der jede Minute startet und 150 Sekunden schläft. Das Protokoll:
07:03:01 start 1562528
07:04:01 start 1563372
07:05:01 start 1564161
07:05:31 end 1562528
07:06:01 start 1565253
Um 7:05:01 liefen drei Kopien gleichzeitig. Cron hat das nicht einmal mit einer Warnung quittiert.

Die Lösung heißt flock (Teil von util-linux, auf jedem Linux vorhanden). Es nimmt eine Sperrdatei und startet den Befehl nur, wenn niemand sonst die Sperre hält:
* * * * * flock -n /tmp/meinjob.lock /pfad/zum/skript.sh
-n heißt: Wenn die Sperre belegt ist, sofort aufgeben, statt zu warten. Unser zweiter Testjob mit derselben Laufzeit, diesmal mit flock -n:
07:03:01 locked-run
07:04:01 skipped
07:05:01 skipped
Genau eine Ausführung, die anderen wurden sauber übersprungen. Weil das Betriebssystem die Sperre beim Prozessende freigibt, bleibt sie auch nach einem Absturz nicht hängen; eine liegengebliebene .lock-Datei ist dabei kein Problem.
In unserer eigenen Crontab nutzt übrigens keiner der 41 Jobs flock. Bei den meisten ist das in Ordnung, weil sie Sekunden dauern und alle paar Minuten laufen. Aber genau das ist die Annahme, die man nirgends prüft: Ein Skript, das alle drei Minuten läuft und an einem schlechten Tag vier Minuten braucht, stapelt sich still auf.
Weitere nützliche Begleiter:
# Nach spätestens 10 Minuten abbrechen
*/15 * * * * timeout 600 /pfad/skript.sh
# Mit niedriger Priorität laufen lassen (CPU und Festplatte)
0 3 * * * nice -n 10 ionice -c3 /pfad/backup.sh
# Kombiniert
0 3 * * * flock -n /tmp/backup.lock timeout 2h nice -n 10 /pfad/backup.sh >> /var/log/backup.log 2>&1
Zeitzonen und Sommerzeit
Cron rechnet in der Systemzeitzone. Welche das ist, verrät timedatectl. Unsere Server laufen, wie die meisten Cloud-Server, auf UTC. 0 4 * * * heißt dann 4 Uhr UTC, also im Sommer 6 Uhr und im Winter 5 Uhr deutscher Zeit. Wer Jobs für Menschen plant („Bericht um 8 Uhr morgens”), muss das umrechnen oder die Zeitzone setzen.
Läuft ein Server dagegen auf Europe/Berlin, kommen die Zeitumstellungen ins Spiel: In der Nacht im März fehlt die Stunde von 2 bis 3 Uhr, im Oktober gibt es sie doppelt. Debian-Cron versucht, beides abzufangen (übersprungene Jobs werden nachgeholt, doppelte nicht zweimal gestartet), andere Cron-Varianten handhaben das unterschiedlich. Die einfachste Regel: Wichtige Jobs nicht zwischen 2 und 3 Uhr nachts planen, wenn der Server nicht auf UTC läuft.
Manche Cron-Versionen (cronie auf Fedora und Rocky Linux) verstehen in der Crontab CRON_TZ=Europe/Berlin. Das Debian-Cron auf Ubuntu ignoriert diese Variable. Verlass dich also nicht darauf, ohne es getestet zu haben.
Ein Einmal-Job ist ein Jahres-Job
Cron kennt keine Jahreszahl. Wer einen einmaligen Termin als Cronjob anlegt, etwa eine Erinnerung am 23. März um 21 Uhr:
0 21 23 3 * befehl
bekommt diese Erinnerung jedes Jahr am 23. März, bis jemand die Zeile löscht. In unserer eigenen root-Crontab haben wir beim Durchsehen sechs solcher Zeilen gefunden: Erinnerungen und Einmal-Aktionen aus dem Frühjahr und Sommer, deren Anlass längst vorbei ist. Zwei davon würden im kommenden März eine Nachricht an eine Telefonnummer schicken, die es nicht mehr gibt. Eine weitere kopiert am 25. Juli eine Sicherungsdatei zurück. Alle sechs liegen still, bis ihr Datum wiederkommt, und dann tun sie etwas, das niemand mehr will.
Für echte Einmal-Aufgaben gibt es zwei bessere Werkzeuge:
# at: einmal ausführen, dann vergessen
echo "/pfad/skript.sh" | at 21:00 2026-10-24
# systemd-run: einmaliger Timer, taucht in systemctl list-timers auf
sudo systemd-run --on-calendar="2026-10-24 21:00" /pfad/skript.sh
Wer trotzdem Cron nimmt, lässt den Job sich selbst entfernen, oder schreibt zumindest das Jahr als Kommentar dazu, damit man beim nächsten Aufräumen weiß, was weg kann.
Sicherheit: wer darf Cronjobs anlegen?
Ein paar Punkte, die man kennen sollte:
- Jobs in der root-Crontab laufen als root. Ein Skript, das root alle fünf Minuten ausführt, aber das ein normaler Benutzer bearbeiten darf, ist eine offene Tür. Prüfe die Rechte der aufgerufenen Skripte:
ls -l /pfad/skript.shsollte als Besitzerrootzeigen und für andere nicht beschreibbar sein. /etc/cron.allowund/etc/cron.denysteuern, welche Benutzercrontabüberhaupt benutzen dürfen. Existiertcron.allow, dürfen nur die dort genannten. Auf Mehrbenutzer-Systemen sinnvoll, auf einem Ein-Personen-Server meist unnötig.- Keine Passwörter in Crontab-Zeilen. Jeder Prozess auf dem System sieht mit
psdie vollständige Kommandozeile laufender Jobs, und die Zeile steht im Cron-Log. Geheimnisse gehören in eine Datei mit Rechten600, die das Skript einliest. - Cron ist ein beliebtes Versteck für Angreifer. Wer einen Server übernimmt, legt gern einen Cronjob an, der die Hintertür regelmäßig wiederherstellt. Bei einem Sicherheitsvorfall gehört
crontab -lfür jeden Benutzer,/etc/crontabund/etc/cron.d/zu den ersten Stellen, die man ansieht. Mehr dazu in unserem Bericht über einen Server, der fünf Tage gekapert war.
Alle Benutzer-Crontabs auf einen Blick:
sudo ls -l /var/spool/cron/crontabs/
for u in $(sudo ls /var/spool/cron/crontabs/); do echo "== $u"; sudo crontab -u "$u" -l; done
Bei uns gibt es dort zwei Benutzer: root und einen Dienstbenutzer für ein Projekt, der alle vier Stunden seinen Zugangsschlüssel erneuert.
Englisch, aber sehr gründlich: Jay LaCroix von Learn Linux TV erklärt Cron Schritt für Schritt, inklusive Systemdateien.
Cronjob vs. systemd Timer vs. anacron
Cron ist nicht die einzige Möglichkeit, Dinge zeitgesteuert zu starten. Auf modernen Linux-Systemen gibt es zwei Alternativen:
| Cron | systemd Timer | anacron | |
|---|---|---|---|
| Aufwand für einen Job | 1 Zeile | 2 Dateien | 1 Zeile |
| Logs | selbst umleiten | automatisch im Journal | selbst umleiten |
| Verpasste Läufe nachholen | nein | ja (Persistent=true) | ja, das ist sein Zweck |
| Überlappung | möglich, flock nötig | ausgeschlossen | – |
| Genauigkeit | Minute | Sekunde und genauer | Tag |
| Ressourcen begrenzen | mit nice/timeout | MemoryMax=, CPUQuota= | nein |
| Abhängigkeiten (Netzwerk, DB) | nein | After=, Requires= | nein |
| Übersicht | crontab -l | systemctl list-timers | /etc/anacrontab |
anacron ist für Rechner gedacht, die nicht immer laufen (Laptops, Desktops): Es merkt sich, wann ein täglicher Job zuletzt lief, und holt ihn beim nächsten Start nach. Auf einem Server, der durchläuft, braucht man es selten.
systemd Timer haben wir im Artikel systemd Service erstellen ausführlich getestet. Sie lösen fast alle Probleme dieses Artikels von selbst: Ausgabe landet im Journal, Läufe überlappen nie, die Umgebung ist klar definiert, und systemctl list-timers zeigt, wann der nächste Lauf ist und wann der letzte war.
Unsere ehrliche Praxis: Cron für Kleinkram, Timer für alles Wichtige. Ein Gesundheitscheck alle fünf Minuten, ein Cache-Aufräumer, ein kurzes Synchronisationsskript: Cron, eine Zeile, fertig. Backups und alles, bei dem ein stilles Versagen wirklich wehtut: Timer.
Cronjobs ohne eigenen Server
Nicht jeder hat Root-Zugang. Drei typische Fälle:
- Shared Hosting (Webhoster mit Admin-Oberfläche): Fast alle Hoster bieten im Kundenmenü einen Punkt „Cronjobs” oder „Geplante Aufgaben”. Dort trägst du die Zeitangabe und den Befehl oder eine URL ein. Die Felder sind dieselben fünf wie in diesem Artikel.
- WordPress und andere Web-Anwendungen haben oft einen eigenen Pseudo-Cron, der nur läuft, wenn jemand die Seite aufruft (
wp-cron.php). Auf Seiten mit wenig Besuchern laufen geplante Aufgaben dann unzuverlässig. Die übliche Lösung: den eingebauten Mechanismus abschalten und einen echten Cronjob einrichten, der die Seite alle fünf Minuten aufruft. - Docker-Container haben normalerweise keinen Cron-Dienst. Entweder den Job auf dem Host per
docker execstarten oder einen eigenen kleinen Container, der nur Cron ausführt. Mehr zu Containern in Was ist Docker.
Fehlersuche: die Checkliste
Wenn ein Cronjob nicht tut, was er soll, gehen wir diese Liste von oben nach unten durch:
- Läuft der Cron-Dienst?
systemctl status cron - Ist der Job eingetragen?
crontab -l(beim richtigen Benutzer!) bzw. die Datei in/etc/cron.d/ - Wurde er gestartet?
journalctl -u cron --since today | grep TEILDESBEFEHLS. Steht dort nichts: Zeitangabe, Dateiname (Punkt?) oder fehlender Zeilenumbruch prüfen. Steht dort ein abgeschnittener Befehl:%escapen. - Was hat er ausgegeben? Ausgabe in eine Logdatei umleiten (
>> /tmp/debug.log 2>&1) und eine Minute warten. - Klappt er in der Cron-Umgebung? Mit dem
env -i-Aufruf von oben testen. Typische Meldung:command not found→ absoluter Pfad oderPATHsetzen. - Stimmen die Rechte? Skript ausführbar (
chmod +x)? Darf der Benutzer auf alle Dateien zugreifen? - Stimmt die Zeit?
timedatectl– läuft der Server vielleicht auf UTC? - Läuft er doppelt?
ps aux | grep skriptname. Falls ja:flock.
Ein Trick zum schnellen Testen: Zeitangabe vorübergehend auf * * * * * setzen, eine Minute warten, Log ansehen, danach zurückstellen. Genau so sind die Tests in diesem Artikel entstanden.
Häufige Fragen
Wie erstelle ich einen Cronjob unter Linux?
Mit crontab -e die eigene Crontab öffnen, eine Zeile mit fünf Zeitfeldern und dem Befehl ans Ende schreiben (zum Beispiel 30 4 * * * /pfad/skript.sh >> /pfad/log.txt 2>&1), speichern. Cron übernimmt die Änderung automatisch, ein Neustart ist nicht nötig.
Was bedeuten die fünf Sterne in der Crontab?
Minute, Stunde, Tag im Monat, Monat, Wochentag – in dieser Reihenfolge. Ein * heißt „jeder Wert”. * * * * * läuft also jede Minute, 0 3 * * * jeden Tag um 3:00 Uhr.
Wie lasse ich einen Cronjob alle 5 Minuten laufen?
Mit */5 * * * * befehl. Er läuft dann zu den Minuten 0, 5, 10, 15 und so weiter jeder Stunde. Alle 15 Minuten entsprechend */15, alle 2 Stunden 0 */2 * * *.
Wo finde ich die Logs von Cronjobs?
Cron protokolliert nur den Start eines Jobs, zu finden mit journalctl -u cron oder in /var/log/syslog. Die Ausgabe des Jobs selbst musst du umleiten, etwa mit >> /var/log/meinjob.log 2>&1 oder | logger -t meinjob, sonst landet sie als Mail beim Benutzer oder verschwindet.
Warum läuft mein Cronjob nicht?
Meist wegen der kargen Umgebung: kurzer PATH, /bin/sh statt Bash, Home-Verzeichnis als Arbeitsverzeichnis, keine Variablen aus .bashrc. Weitere häufige Ursachen: ein unescaptes % in der Zeile, ein Punkt im Dateinamen unter /etc/cron.d/, ein fehlender Zeilenumbruch am Dateiende oder eine falsche Zeitzone.
Muss ich Cron nach einer Änderung neu starten?
Nein. crontab -e meldet die Änderung an Cron, und Dateien in /etc/cron.d/ und /etc/crontab werden jede Minute auf Änderungen geprüft. In unseren Tests liefen neue Dateien ab der nächsten vollen Minute.
Wie verhindere ich, dass ein Cronjob doppelt läuft?
Mit flock -n /tmp/name.lock befehl. Läuft die vorige Ausführung noch, wird die neue sofort übersprungen. Ohne flock startet Cron jede Minute eine neue Kopie, auch wenn die alte noch arbeitet; in unserem Test waren es nach drei Minuten drei gleichzeitige.
Wie lösche ich einen Cronjob?
Mit crontab -e die entsprechende Zeile entfernen oder mit # auskommentieren, dann speichern. crontab -r löscht dagegen die komplette Crontab ohne Rückfrage – vorher mit crontab -l > backup.txt sichern.
Wie führe ich einen Cronjob als anderer Benutzer aus?
Als root mit sudo crontab -u benutzername -e dessen Crontab bearbeiten. Oder in /etc/cron.d/ eine Datei anlegen und den Benutzernamen zwischen Zeitangabe und Befehl schreiben: 30 4 * * * benutzername /pfad/skript.sh.
Kann ein Cronjob jede Sekunde laufen?
Nicht direkt, die kleinste Einheit ist eine Minute. Notlösungen wie mehrere Zeilen mit sleep 10; befehl funktionieren, sind aber unschön. Für Intervalle unter einer Minute ist ein systemd Timer (OnUnitActiveSec=10s) oder ein dauerhaft laufender Dienst die bessere Wahl.
Was ist besser, Cron oder systemd Timer?
Cron ist einfacher (eine Zeile) und überall gleich. systemd Timer bieten Logs im Journal, Nachholen verpasster Läufe, Schutz vor Überlappung und Ressourcen-Grenzen. Für Kleinkram reicht Cron, für Backups und wichtige Aufgaben nehmen wir Timer.
Fazit: eine Zeile für den Job, drei Gedanken für die Ruhe
Einen Cronjob erstellen dauert eine Minute: crontab -e, fünf Zeitfelder, ein Befehl, speichern. Damit läuft er. Was unsere Tests aber gezeigt haben: Cron ist schweigsam, wenn es schiefgeht.
- Cron startet auch dann, wenn der letzte Lauf noch nicht fertig ist. Bei uns liefen drei Kopien parallel, bis
flocksie stoppte. - Ein
%in der Zeile schneidet den Befehl ab, ohne dass irgendwo ein Fehler auftaucht. - Dateien in
/etc/cron.d/mit Punkt im Namen oder ohne letzte Leerzeile werden ignoriert, einmal mit Logzeile, einmal ohne. - Tag und Wochentag zusammen bedeuten ODER, nicht UND.
- Die Ausgabe geht per Mail an eine Adresse, die vielleicht niemand liest – bei uns 273 Rückläufer in einem Monat.
- Ein Einmal-Termin kommt jedes Jahr wieder: sechs vergessene Zeilen in unserer eigenen Crontab.
Die drei Gewohnheiten, die fast alles davon abfangen: absolute Pfade in einem eigenen Skript, Ausgabe immer selbst umleiten, und flock für alles, was länger als ein paar Sekunden dauern kann. Für wirklich wichtige Aufgaben lohnt sich der Umstieg auf einen systemd Timer. Und wenn auf deinem Server SSH-Anmeldungen ins Log laufen, ist Fail2ban ein guter Kandidat für den nächsten Automatismus, gleich nach einer sauberen Firewall mit UFW.