Zum Inhalt springen
SONOYA.com
Raspberry Pi & Linux

Anleitung: Cronjobs unter Raspberry Pi OS einrichten

So richtest Du unter dem aktuellen Raspberry Pi OS (Trixie) zuverlässige Cronjobs ein: Zeitzone und Uhrzeit korrekt setzen, die crontab-Syntax verstehen und Bash-Scripts, Python-Scripts sowie virtuelle Umgebungen automatisch starten. Mit Logging, @reboot und einem Lock-File gegen Doppelausführung per flock…

Von Sonoya Redaktion 9 Min. Lesezeit
Anleitung: Cronjobs unter Raspberry Pi OS einrichten
Anleitung: Cronjobs unter Raspberry Pi OS einrichten

Du willst, dass Dein Raspberry Pi etwas regelmäßig von allein erledigt: ein Backup ziehen, ein Python-Script alle fünf Minuten ausführen, jede Nacht eine Datenbank sichern oder beim Start einen Dienst hochfahren. Genau dafür gibt es cron, den eingebauten Zeitplaner von Raspberry Pi OS. Diese Anleitung zu Cronjobs ist als Nachschlagewerk gedacht: Du findest hier zuerst die Zeiteinstellungen, dann die crontab-Syntax und danach die Muster, die in der Praxis immer wieder vorkommen, vom Bash-Script über das virtuelle Python-Environment bis zum Lock-File, das verhindert, dass derselbe Job zweimal gleichzeitig läuft.

Alle Beispiele beziehen sich auf das aktuelle Raspberry Pi OS, das inzwischen auf Debian 13 „Trixie” mit Python 3.13 basiert. cron ist dort bereits installiert und aktiv, Du musst also nichts nachinstallieren. In den Befehlen steht pi stellvertretend für Deinen Benutzernamen. Der Raspberry Pi Imager legt seit einiger Zeit keinen festen pi-Nutzer mehr an, sondern fragt Deinen Wunschnamen ab. Ersetze pi also überall durch den Namen, den Du bei der Einrichtung vergeben hast.

Zuerst: Datum, Uhrzeit und Zeitzone richtig einstellen

cron entscheidet anhand der lokalen Systemzeit, wann ein Job läuft. Steht die Zeitzone falsch, startet ein nächtlicher Job plötzlich am Nachmittag. Bevor Du irgendetwas planst, lohnt sich also ein Blick auf die Uhr. Den aktuellen Stand zeigt Dir dieser Befehl:

Code
timedatectl

In der Ausgabe interessieren Dich drei Zeilen: Time zone (die eingestellte Zeitzone), System clock synchronized: yes (die Uhr wird über das Netz abgeglichen) und NTP service: active (der Abgleich-Dienst läuft). Der Raspberry Pi hat keine batteriegepufferte Echtzeituhr, er holt sich die korrekte Zeit beim Start aus dem Internet über systemd-timesyncd. Solange System clock synchronized auf yes steht, stimmt die Uhr auf die Sekunde genau.

Die Zeitzone setzt Du für Deutschland auf Berlin:

Code
sudo timedatectl set-timezone Europe/Berlin

Welche Bezeichner es gibt, listet Dir timedatectl list-timezones auf, gefiltert etwa mit timedatectl list-timezones | grep Europe. Falls der Zeitabgleich aus irgendeinem Grund deaktiviert war, schaltest Du ihn so wieder ein:

Code
sudo timedatectl set-ntp true

Wer lieber im Menü arbeitet, erreicht dieselbe Einstellung über sudo raspi-config unter Localisation Options > Timezone. Das Ergebnis ist identisch, timedatectl ist nur der direktere Weg.

Wann eine andere Zeitzone als Berlin sinnvoll ist

Für einen Pi, der bei Dir zu Hause steht und Aufgaben für Dich erledigt, ist Europe/Berlin die richtige Wahl, weil „jeden Tag um 3 Uhr” dann auch wirklich 3 Uhr nachts bei Dir bedeutet. Es gibt aber Situationen, in denen UTC die bessere Wahl ist.

Der häufigste Grund ist die Sommerzeit. Bei der Umstellung im Frühjahr gibt es die Stunde von 2 bis 3 Uhr nicht, im Herbst gibt es sie doppelt. Ein Cronjob, der auf 2:30 Uhr gelegt ist, wird dadurch an einem Tag im Jahr übersprungen und an einem anderen zweimal ausgeführt. Wenn Dein Job genau dann läuft und das ein Problem wäre, etwa bei einer Abrechnung oder einem Datentransfer, stellst Du den Pi auf UTC um, denn UTC kennt keine Sommerzeit:

Code
sudo timedatectl set-timezone UTC

Sinnvoll ist UTC außerdem, wenn der Pi physisch in einem anderen Land steht, wenn Du seine Logs mit anderen Servern vergleichen willst, die ebenfalls in UTC laufen, oder wenn ein Job auf einen Dienst in einer fremden Zeitzone abgestimmt sein muss, zum Beispiel auf die Handelszeiten einer Börse in den USA. Denk in dem Fall daran, dass Deine crontab-Zeiten dann ebenfalls in UTC gemeint sind: 3 Uhr im crontab-Eintrag ist dann 4 oder 5 Uhr in Deutschland, je nach Jahreszeit.

So funktioniert die crontab

Jeder Benutzer hat seine eigene crontab, eine einfache Textdatei mit den geplanten Aufgaben. Du bearbeitest sie nicht direkt, sondern über diesen Befehl:

Code
crontab -e

Beim ersten Aufruf fragt Dich Raspberry Pi OS, welchen Editor Du nutzen möchtest. Wähl nano, das ist die einfachste Option. Du speicherst darin später mit Strg + O, bestätigst mit Enter und schließt den Editor mit Strg + X.

Jede Zeile in der crontab beschreibt einen Job. Sie besteht aus fünf Zeitfeldern und danach dem Befehl, der ausgeführt werden soll. Die fünf Felder stehen für Minute, Stunde, Tag des Monats, Monat und Wochentag:

Code
# ┌───────── Minute (0-59)
# │ ┌─────── Stunde (0-23)
# │ │ ┌───── Tag des Monats (1-31)
# │ │ │ ┌─── Monat (1-12)
# │ │ │ │ ┌─ Wochentag (0-7, 0 und 7 sind Sonntag)
# │ │ │ │ │
# * * * * * auszuführender Befehl

Ein * bedeutet „jeder zulässige Wert”. Daneben gibt es drei Schreibweisen, die Du ständig brauchst: */5 im Minutenfeld heißt „alle 5 Minuten”, 1,15,30 heißt „zu Minute 1, 15 und 30” und 9-17 im Stundenfeld heißt „von 9 bis 17 Uhr”. Damit lässt sich fast jeder Zeitplan ausdrücken:

Code
*/5 * * * *    alle 5 Minuten
0 * * * *      jede volle Stunde
30 3 * * *     täglich um 3:30 Uhr
0 4 * * 0      jeden Sonntag um 4:00 Uhr
0 2 1 * *      am 1. jedes Monats um 2:00 Uhr
15 9-17 * * 1-5  werktags zwischen 9 und 17 Uhr jeweils zur Minute 15

Für die gängigen Intervalle gibt es zusätzlich Kürzel, die sich leichter lesen: @hourly, @daily (entspricht 0 0 * * *), @weekly, @monthly und @reboot für „einmal beim Hochfahren”. Was aktuell in Deiner crontab steht, zeigt Dir crontab -l. Mit crontab -r löschst Du die komplette crontab, sei damit vorsichtig, denn der Befehl fragt nicht nach. Wenn Du nur einen Eintrag entfernen willst, öffne die crontab lieber mit crontab -e und lösch die betreffende Zeile.

Beispiel 1: Ein Bash-Script regelmäßig ausführen

Das häufigste Szenario ist ein Shell-Script, das Du in festem Takt laufen lassen willst. Leg es zum Beispiel unter /home/pi/scripts/backup.sh an. Wichtig ist die erste Zeile, die sogenannte Shebang, die festlegt, mit welcher Shell das Script läuft:

Code
#!/bin/bash
rsync -a /home/pi/daten/ /home/pi/backup/

Danach machst Du das Script ausführbar:

Code
chmod +x /home/pi/scripts/backup.sh

In der crontab (crontab -e) trägst Du den Job mit dem vollständigen Pfad zum Script ein. Hier läuft das Backup jeden Tag um 2:30 Uhr:

Code
30 2 * * * /home/pi/scripts/backup.sh

Wenn Du noch nie ein Shell-Script geschrieben hast, hilft Dir die Linux-Anleitung: Erstes Shell Script schreiben zur Systemaktualisierung (Ubuntu) beim Einstieg, sie gilt für Raspberry Pi OS genauso.

Beispiel 2: Ein Python-Script starten

Ein Python-Script ohne weitere Abhängigkeiten startest Du, indem Du den Python-Interpreter mit dem absoluten Pfad zum Script aufrufst. Verlass Dich nicht auf python3 allein, denn cron findet diesen Befehl je nach Umgebung nicht zuverlässig. Schreib stattdessen den vollen Pfad /usr/bin/python3 hin:

Code
*/10 * * * * /usr/bin/python3 /home/pi/scripts/sensor.py

Dieser Eintrag führt das Script alle 10 Minuten aus. Damit das funktioniert, muss Dein Code mit absoluten Pfaden auf seine Dateien zugreifen, denn cron startet den Job nicht in Deinem Projektordner, sondern in Deinem Home-Verzeichnis. Schreib also nicht open("daten.csv"), sondern open("/home/pi/scripts/daten.csv"), oder ermittle das Verzeichnis im Script selbst.

Beispiel 3: Python-Script in einem virtual environment

Sobald Dein Script externe Pakete wie requests oder pandas braucht, kommst Du um eine virtuelle Umgebung nicht herum. Das aktuelle Raspberry Pi OS verhindert nämlich bewusst, dass Du Pakete systemweit mit pip installierst, und zeigt stattdessen die Meldung externally-managed-environment. Eine virtuelle Umgebung kapselt die Pakete sauber pro Projekt und löst das Problem.

Leg die Umgebung einmalig an. Wechsle in Deinen Projektordner, erzeuge das Environment und installiere die Pakete:

Code
cd /home/pi/wetter
python3 -m venv .venv
source .venv/bin/activate
pip install requests
deactivate

Sollte beim Anlegen eine Fehlermeldung zum fehlenden venv-Modul erscheinen, installierst Du es mit sudo apt install python3-venv nach und wiederholst den Befehl.

Der entscheidende Punkt für cron: In der crontab brauchst Du die Umgebung nicht mit source activate zu aktivieren. Das funktioniert in der minimalen Shell von cron ohnehin schlecht. Stattdessen rufst Du direkt den Python-Interpreter aus dem Environment auf. Dieser Interpreter benutzt automatisch alle Pakete, die Du dort installiert hast:

Code
*/30 * * * * /home/pi/wetter/.venv/bin/python /home/pi/wetter/wetter.py

Der Pfad /home/pi/wetter/.venv/bin/python ist der Schlüssel: Er ersetzt das Aktivieren komplett. Wenn Du tiefer in das Thema robuste Python-Scripts einsteigen willst, etwa um Netzwerkfehler sauber abzufangen, sind die Die besten Tipps & Tricks für robuste API Calls mit Python Skripten eine gute Ergänzung.

Warum ein Job im Terminal läuft, aber nicht in cron

Das ist der mit Abstand häufigste Stolperstein, und er hat fast immer dieselbe Ursache. cron startet Deine Jobs in einer bewusst minimalen Umgebung. Es liest weder Deine .bashrc noch Dein Profil, der Suchpfad PATH enthält nur /usr/bin und /bin, und das Arbeitsverzeichnis ist Dein Home-Ordner. Ein Befehl, der bei Dir im Terminal funktioniert, weil er unter /usr/local/bin liegt oder weil eine Umgebungsvariable gesetzt ist, scheitert deshalb in cron stillschweigend.

Drei Maßnahmen lösen das zuverlässig. Erstens: Verwende in crontab und Scripts immer absolute Pfade, sowohl zu den Programmen als auch zu den Dateien. Zweitens: Wenn Dein Script unbedingt aus seinem eigenen Ordner laufen muss, wechsle dort hinein, bevor es loslegt, etwa mit cd /home/pi/projekt als erster Zeile nach der Shebang. Drittens kannst Du am Anfang der crontab Variablen setzen, die dann für alle Jobs gelten:

Code
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""

Damit findet cron auch Programme unter /usr/local/bin, und das leere MAILTO unterdrückt die internen Mail-Benachrichtigungen, die cron sonst bei jeder Ausgabe zu verschicken versucht.

Wenn Du einen Job testen willst, ohne auf den nächsten Zeitpunkt zu warten, simulierst Du die abgespeckte Umgebung von cron mit env -i:

Code
env -i /bin/sh -c '/home/pi/scripts/backup.sh'

Läuft das Script auch unter diesen kargen Bedingungen durch, läuft es auch in cron.

Ausgabe und Fehler protokollieren

Standardmäßig verschwindet alles, was ein Cronjob ausgibt. Wenn etwas schiefgeht, weißt Du also nicht, warum. Deshalb solltest Du die Ausgabe in eine Logdatei umleiten. Das Konstrukt >> /pfad/log 2>&1 hängt die normale Ausgabe an die Datei an und leitet die Fehlerausgabe 2 an dieselbe Stelle &1 um, sodass Du beides an einem Ort hast:

Code
30 2 * * * /home/pi/scripts/backup.sh >> /home/pi/logs/backup.log 2>&1

Damit das Verzeichnis existiert, legst Du es einmal mit mkdir -p /home/pi/logs an. Wenn Du im Script selbst mit einem Zeitstempel arbeitest, etwa indem Du eine Zeile mit date ausgibst, siehst Du in der Logdatei später genau, wann welcher Lauf passiert ist.

Davon getrennt protokolliert cron auch selbst, dass und wann es einen Job gestartet hat. Diese Meldungen findest Du unter Trixie nicht mehr in /var/log/syslog, weil das aktuelle Raspberry Pi OS standardmäßig kein rsyslog mehr mitbringt und alles ins systemd-Journal schreibt. Die Cron-Einträge liest Du daher so:

Code
journalctl -u cron

Mit journalctl -u cron -f verfolgst Du die Meldungen live, was beim Einrichten praktisch ist, weil Du sofort siehst, ob cron Deinen Job überhaupt anfasst. Den alten Befehl grep CRON /var/log/syslog brauchst Du auf einem frischen Trixie-System nicht mehr zu probieren, die Datei ist dort in der Regel gar nicht vorhanden.

Beim Booten starten mit @reboot

Manche Aufgaben sollen nicht zu einer Uhrzeit laufen, sondern einmalig, sobald der Pi hochgefahren ist. Dafür gibt es das Kürzel @reboot. Häufig hängt der Job allerdings von einem Netzwerk oder anderen Diensten ab, die kurz nach dem Start noch nicht bereit sind. Eine kurze Wartezeit löst das zuverlässig:

Code
@reboot sleep 30 && /home/pi/scripts/start.sh >> /home/pi/logs/start.log 2>&1

Der Pi wartet damit 30 Sekunden nach dem Start, bevor er das Script ausführt. Für einen kleinen Hilfs-Job ist @reboot völlig ausreichend. Wenn Du allerdings einen echten Dienst betreibst, der dauerhaft laufen und nach einem Absturz neu starten soll, ist ein systemd-Service die robustere Wahl.

Verhindern, dass ein Cronjob doppelt läuft

Hier liegt eine Falle, die viele erst spät bemerken. Stell Dir einen Job vor, der alle fünf Minuten startet, aber gelegentlich acht Minuten braucht, etwa weil ein Backup größer ausfällt oder eine Antwort aus dem Netz auf sich warten lässt. cron schert sich nicht darum, ob der vorige Lauf schon fertig ist, und startet trotzdem den nächsten. Plötzlich laufen zwei Kopien gleichzeitig, schreiben sich gegenseitig die Dateien kaputt oder verdoppeln die Last.

Die saubere Lösung dafür ist flock. Das Programm legt eine Sperrdatei an und gibt sie erst frei, wenn der Job durch ist. Startet cron in der Zwischenzeit einen zweiten Lauf, sieht der die Sperre und beendet sich sofort, ohne Schaden anzurichten. Du musst Dein Script dafür nicht ändern, sondern stellst flock in der crontab einfach davor:

Code
*/5 * * * * /usr/bin/flock -n /tmp/backup.lock /home/pi/scripts/backup.sh

Die Option -n bedeutet „nicht warten”: Ist die Sperre /tmp/backup.lock bereits belegt, gibt flock sofort auf, statt sich anzustellen. Genau das willst Du, denn ein überlappender Lauf soll ja gerade nicht stattfinden. Lässt Du -n weg, wartet der zweite Lauf stattdessen, bis der erste fertig ist, und startet dann verzögert, was bei manchen Aufgaben ebenfalls sinnvoll sein kann. Ein großer Vorteil von flock: Wird der Job abgebrochen oder stürzt der Pi mitten im Lauf ab, gibt das Betriebssystem die Sperre automatisch wieder frei, es bleibt also keine verwaiste Lock-Datei zurück, die alles blockiert.

Kurz zusammengefasst

Mit diesen Bausteinen deckst Du praktisch jede wiederkehrende Aufgabe auf dem Raspberry Pi ab: Du stellst zuerst Zeitzone und Uhrzeit über timedatectl korrekt ein, planst Deine Jobs mit der Fünf-Felder-Syntax in crontab -e, rufst Scripts und Interpreter immer mit absolutem Pfad auf, leitest die Ausgabe in eine Logdatei um und schützt empfindliche Jobs mit flock gegen Doppelausführung. Wenn Dein Pi rund um die Uhr durchläuft und solche Aufgaben erledigt, lohnt es sich, ihn auch hardwareseitig dafür zu rüsten. Wie das geht, zeigt Dir die Anleitung: Raspberry Pi Einstellungen für den 24/7 Dauerbetrieb optimieren. Weil ein Pi im Dauerbetrieb seine Speicherkarte stark beansprucht, ist dafür eine zuverlässige microSD-Karte (Affiliate Link) mit Endurance-Auslegung eine sinnvolle Grundlage.

Weiterlesen .