Cronjobs überwachen — und warum sie stillschweigend ausfallen
Ein Cronjob, der nicht mehr läuft, erzeugt weder Fehler noch Ausgabe. Warum das so ist — und wie Heartbeat-Monitoring es aufdeckt.
Die meisten Monitoring-Ansätze beantworten die Frage „antwortet dieser Server?“. Bei Cronjobs ist die Frage umgekehrt: „Ist etwas passiert, das hätte passieren sollen?“ Niemand verbindet sich mit einem Cronjob — es gibt keinen Endpunkt zum Abfragen und keinen Fehler zum Abfangen. Wenn ein nächtliches Backup ausfällt, sieht die Stille genauso aus wie Erfolg.
Vier Arten, wie ein Cronjob ausfällt, ohne es zu sagen
- Er startet nie. Der Crontab-Eintrag ging bei einer Server-Migration verloren, das Benutzerkonto wurde deaktiviert oder cron selbst läuft nicht. Für einen nie aufgerufenen Job gibt es keine Logzeile.
- Er startet und stirbt sofort. Cron läuft mit minimaler Umgebung — oft ohne PATH über /usr/bin:/bin hinaus und ohne dein Shell-Profil. Ein Skript, das von Hand funktioniert, scheitert unter cron mit „command not found“.
- Er läuft, tut aber nichts Sinnvolles. Der Job endet mit 0, nachdem er keine Arbeit gefunden hat, weil sich ein Pfad geändert hat. Formal war er erfolgreich.
- Er läuft endlos. Der vorige Durchlauf ist noch aktiv, wenn der nächste startet; beide konkurrieren um dieselbe Sperre, bis dem Rechner der Speicher ausgeht.
Heartbeat-Monitoring: die Prüfung umdrehen
Statt dass etwas den Job prüft, meldet sich der Job selbst, wenn er fertig ist. Du bekommst eine eindeutige URL, der Job ruft sie bei Erfolg auf, und der Monitor schlägt Alarm, wenn der Aufruf nicht im erwarteten Zeitfenster eintrifft. Das Ausbleiben des Signals ist das Signal.
Entscheidend ist die Platzierung: Setze den Aufruf ans Ende des Jobs, nach der eigentlichen Arbeit — so wird er nur ausgelöst, wenn die Arbeit wirklich abgeschlossen wurde.
# Wrong: fires even if the backup failed.
0 3 * * * curl -fsS https://api.hypermonitors.com/v1/heartbeat/YOUR_TOKEN; /usr/local/bin/backup.sh
# Right: only fires when backup.sh exits 0.
0 3 * * * /usr/local/bin/backup.sh && curl -fsS https://api.hypermonitors.com/v1/heartbeat/YOUR_TOKENIn einem Skript statt in der Crontab-Zeile — dieselbe Idee, mit erhaltenem Exit-Code:
#!/usr/bin/env bash
set -euo pipefail
HEARTBEAT="https://api.hypermonitors.com/v1/heartbeat/YOUR_TOKEN"
do_the_work
# --max-time so a hung request can never wedge the job itself.
curl -fsS --max-time 10 --retry 3 "$HEARTBEAT" > /dev/nullDie Karenzzeit wählen
Die Karenzzeit legt fest, wie lange der Monitor nach dem erwarteten Zeitpunkt wartet, bevor er Alarm schlägt. Zu knapp, und ein gelegentlich längerer Job weckt dich um drei Uhr nachts grundlos. Zu großzügig, und ein wirklich toter Job bleibt stundenlang unentdeckt.
Eine brauchbare Faustregel: normale Laufzeit plus Intervall, dann noch die Hälfte als Puffer. Ein stündliches Backup, das normalerweise vier Minuten braucht, bekommt rund 90 Minuten Karenz — lang genug für eine langsame Nacht, kurz genug, dass ein ausgefallener Lauf vor dem nächsten Arbeitstag auffällt.
Was Heartbeats nicht abfangen
Die Grenzen sollte man klar benennen. Ein Heartbeat beweist, dass der Job seine letzte Zeile erreicht hat — nicht, dass er das Richtige getan hat. Ein Backup, das eine Null-Byte-Datei schreibt und mit 0 endet, meldet sich jede Nacht fröhlich. Wenn dieser Unterschied zählt, prüfe das Ergebnis vor dem Ping: Dateigröße, Zeilenzahl, ob sich das Archiv öffnen lässt. Erst dann bedeutet der Heartbeat etwas.
pg_dump mydb | gzip > /backups/db.sql.gz
# Only ping if the dump is plausibly sized (here: over 1 MB).
if [ "$(stat -c%s /backups/db.sql.gz)" -gt 1000000 ]; then
curl -fsS --max-time 10 "$HEARTBEAT" > /dev/null
fiContainer, Kubernetes und Serverless
Das Muster ist überall gleich — genau das ist der Vorteil: Ein Heartbeat braucht nur ausgehendes HTTP. Ein Kubernetes-CronJob pingt im letzten Container-Befehl, eine Lambda am Ende des Handlers, ein GitHub-Actions-Workflow in einem letzten Schritt mit `if: success()`. Nichts muss durch deine Firewall hereinkommen.