Zum Inhalt springen
Serverküche
Suche

Die Suche wird geladen … (nur in der veröffentlichten Seite verfügbar).

Anwendungen Schwierigkeit: Fortgeschritten

Healthchecks: Cron-Jobs & Backups überwachen (Dead-Man-Switch)

Merkt, wenn ein Backup NICHT läuft: Healthchecks überwacht Cron-Jobs per Dead-Man-Switch und alarmiert bei Ausfall – selbst gehostet hinter Traefik.

· 7 Min. Lesezeit ·Dauer: ca. 45 Minuten
Inhaltsverzeichnis

Die gefährlichsten Ausfälle sind die stillen: Das nächtliche Backup läuft seit drei Wochen nicht mehr – und niemand merkt es, bis man die Daten wirklich braucht. Uptime Kuma sagt dir, wenn ein Dienst da ist. Healthchecks sagt dir, wenn ein Job nicht gelaufen ist. Genau das ist der Unterschied, der Daten rettet.

Was bauen wir?

Einen selbst gehosteten Healthchecks-Server (v4.4) hinter Traefik, der als Dead-Man-Switch funktioniert: Jeder überwachte Job „meldet sich“ nach erfolgreichem Lauf mit einem kurzen HTTP-Ping. Bleibt dieser Ping aus (weil der Job abgestürzt ist, der Server aus war oder der Cron-Eintrag fehlt), schlägt Healthchecks Alarm. Am Ende überwachst du damit deine Restic-Backups, Datenbank-Dumps und jeden anderen wiederkehrenden Job – und wirst benachrichtigt, bevor das Fehlen auffällt.

Voraussetzungen

🍳 Empfehlung Anzeige

VPS 1000 G12.5

4 vCore · 8 GB RAM · 128 GB SSD

ab 14,50 €/Monat

Healthchecks ist genügsam und läuft gut neben deinem übrigen Stack.

Zu netcup →

💶 5 € Gutschein für netcup-Neukunden: (nicht für Domains und VPS Lite)

Im Warenkorb einlösen →

Schritt für Schritt

Schritt 1: Das Prinzip verstehen – ein umgedrehtes Monitoring

Klassisches Monitoring fragt aktiv: „Antwortet der Dienst?“ Healthchecks dreht das um: Der Job meldet sich beim Server. Jeder Check hat eine eindeutige Ping-URL. Nach erfolgreichem Lauf ruft der Job diese URL auf. Healthchecks erwartet den Ping in einem festgelegten Zeitfenster (Period) plus einer Toleranz (Grace Time). Kommt der Ping nicht rechtzeitig, gilt der Check als „down“ und Healthchecks alarmiert. Das ist der Dead-Man-Switch: Nicht das Vorhandensein eines Signals löst Alarm aus, sondern sein Ausbleiben.

Schritt 2: Compose-Datei anlegen

Healthchecks ist eine Django-Anwendung; wir betreiben sie mit SQLite – für den typischen Selfhosting-Umfang völlig ausreichend. Lege das Projekt an:

Terminal
mkdir -p /opt/healthchecks/data && cd /opt/healthchecks
chown -R 1000:1000 data

Die compose.yaml – ersetze DEINE_DOMAIN und erzeuge einen eigenen SECRET_KEY (openssl rand -hex 32):

YAML
services:
  healthchecks:
    image: healthchecks/healthchecks:v4.4
    restart: unless-stopped
    user: "1000:1000"
    volumes:
      - ./data:/data
    environment:
      SITE_ROOT: https://DEINE_DOMAIN
      SITE_NAME: Serverkueche Healthchecks
      ALLOWED_HOSTS: DEINE_DOMAIN
      CSRF_TRUSTED_ORIGINS: https://DEINE_DOMAIN
      DEBUG: "False"
      DB: sqlite
      DB_NAME: /data/hc.sqlite
      SECRET_KEY: "DEIN_ZUFAELLIGER_SCHLUESSEL"
    networks: [proxy]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.hc.rule=Host(`DEINE_DOMAIN`)"
      - "traefik.http.routers.hc.entrypoints=websecure"
      - "traefik.http.routers.hc.tls.certresolver=le"
      - "traefik.http.services.hc.loadbalancer.server.port=8000"

networks:
  proxy:
    external: true

Zwei Fallen: DB_NAME und keine Umlaute in SITE_NAME

Bei SQLite ist DB_NAME: /data/hc.sqlite Pflicht – ohne den Pfad legt Healthchecks die Datenbank an einer nicht beschreibbaren Stelle an und startet mit „unable to open database file“ nicht. Und: Schreibe SITE_NAME rein in ASCII (kein „ü“, „ö“ …). Ein Umlaut in dieser Umgebungsvariable führt bei aktuellem Python zu einem UnicodeEncodeError: surrogates not allowed – die Seite quittiert dann mit HTTP 500 (genau das ist mir beim Testen dieses Tutorials passiert).

Schritt 3: Starten und anmelden

Terminal
docker compose up -d

Das Image bringt einen Healthcheck mit; Traefik leitet erst weiter, wenn der Container healthy ist (etwa 20–30 Sekunden). Prüfe:

Terminal
docker compose ps
Ausgabe
NAME                          IMAGE                        STATUS
healthchecks-healthchecks-1   healthchecks/healthchecks:v4.4  Up (healthy)

Ein Konto bringt Healthchecks nicht von selbst mit – das Image legt beim Start nur die Datenbank an. Leg deinen Zugang deshalb einmalig selbst an (Passwort ersetzen):

Terminal
docker compose exec healthchecks python manage.py createsuperuser \
  --email admin@DEINE_DOMAIN --password DEIN_STARKES_PASSWORT
Ausgabe
Superuser created successfully.

SUPERUSER_EMAIL wirkt nicht

In vielen Anleitungen stehen SUPERUSER_EMAIL und SUPERUSER_PASSWORD als Umgebungsvariablen in der Compose-Datei. Das offizielle Image liest sie nicht – sein Start-Hook führt ausschließlich manage.py migrate aus. Wer sich darauf verlässt, steht vor einer Anmeldeseite, an der kein Konto existiert (getestet mit v4.3 und v4.4). Der Weg über createsuperuser oben ist der verlässliche.

Öffne jetzt https://DEINE_DOMAIN/ und melde dich mit diesen Zugangsdaten an:

Die Anmeldeseite von Healthchecks unter der eigenen HTTPS-Domain
Der selbst gehostete Healthchecks-Login – das Konto stammt aus dem createsuperuser-Aufruf

Schritt 4: Ein Projekt und den ersten Check anlegen

Nach dem Login legst du über New Project… ein Projekt an (z. B. „Serverküche“) und darin über Add Check deinen ersten Check. Gib ihm einen sprechenden Namen, Tags und einen Zeitplan – Period = erwarteter Abstand zwischen zwei Läufen (für ein tägliches Backup: 1 Tag), Grace Time = wie lange Healthchecks nach dem Fälligkeitszeitpunkt noch wartet, bevor es Alarm schlägt (z. B. 1 Stunde). Die Übersicht zeigt alle Checks mit Status, Ping-URL und letztem Ping:

Die Healthchecks-Übersicht mit mehreren Checks, Ping-URLs und Status-Anzeige
Die Check-Übersicht: grüner Haken für „läuft“, dazu Ping-URL, Zeitplan und letzter Ping

Jeder Check bekommt eine eigene Ping-URL der Form https://DEINE_DOMAIN/ping/<UUID>. Ein Klick auf den Check öffnet die Detailseite mit Anleitung, Verlauf und Status:

Die Detailseite eines Checks mit Ping-URL, aktuellem Status „up“ und Ereignisprotokoll
Die Detailseite: Ping-URL, aktueller Status und das Protokoll der eingegangenen Pings

Schritt 5: Einen Job den Ping senden lassen

Jetzt der Kern. Deinen Job lässt du nach erfolgreichem Lauf die Ping-URL aufrufen. Das einfachste Beispiel – am Ende deines Skripts:

Terminal
curl -fsS -m 10 --retry 5 https://DEINE_DOMAIN/ping/DEINE_CHECK_UUID

-fsS macht curl leise, aber meldet Fehler; -m 10 bricht nach 10 Sekunden ab; --retry 5 fängt kurze Netzwerkaussetzer ab. Noch besser: den Exit-Code des Jobs mitmelden, damit ein fehlgeschlagener Lauf sofort als Fehler erscheint statt als „kein Ping“:

Terminal
#!/bin/bash
URL="https://DEINE_DOMAIN/ping/DEINE_CHECK_UUID"
# ... hier läuft dein eigentlicher Job ...
restic backup /wichtige/daten
# Exit-Code an Healthchecks melden (0 = ok, sonst Fehler)
curl -fsS -m 10 --retry 5 "$URL/$?"

Schritt 6: Restic-Backups überwachen

Das ist der Paradefall. Wenn deine Restic-Backups per systemd-Timer laufen, ergänzt du den Ping am Ende des Backup-Skripts. Läuft das Backup nicht (Timer deaktiviert, Server aus, Skript abgestürzt), bleibt der Ping aus – und nach Ablauf der Grace Time alarmiert Healthchecks. So erfährst du von einem toten Backup nach Stunden, nicht erst beim Datenverlust.

Start- und Fehlersignale nutzen

Healthchecks kann mehr als „fertig“: Ein Ping auf .../ping/UUID/start vor dem Job misst zusätzlich die Laufzeit, ein Ping auf .../ping/UUID/fail meldet aktiv einen Fehlschlag. So siehst du nicht nur ob, sondern auch wie lange ein Job lief – nützlich, um schleichend langsamer werdende Backups zu erkennen.

Schritt 7: Benachrichtigungen einrichten

Ein Alarm nützt nur, wenn er dich erreicht. Unter Integrations verbindest du Kanäle: E-Mail (dafür SMTP-Umgebungsvariablen setzen), ntfy, Telegram, Webhooks und viele mehr. Für den Selfhosting-Stack ist ntfy die naheliegende Wahl – Push aufs Handy, ohne fremden Dienst. Richte mindestens einen Kanal ein und weise ihn deinen Checks zu, sonst bleibt der „down“-Status stumm.

Wenn es nicht funktioniert

Container startet nicht: „unable to open database file“. DB_NAME: /data/hc.sqlite fehlt oder der data-Ordner gehört nicht UID 1000. Pfad setzen und chown -R 1000:1000 /opt/healthchecks/data (siehe Warnung in Schritt 2).

Die Seite antwortet mit HTTP 500. Häufigste Ursache: ein Umlaut in SITE_NAME (oder einer anderen Text-Umgebungsvariable). Auf reines ASCII umstellen und neu starten. Zur Diagnose vorübergehend DEBUG: "True" setzen – aber danach wieder auf False.

Login schlägt fehl / CSRF-Fehler beim Absenden. CSRF_TRUSTED_ORIGINS: https://DEINE_DOMAIN muss gesetzt sein – Django lehnt sonst POST-Anfragen hinter dem Reverse Proxy ab. Und ALLOWED_HOSTS muss exakt deine Domain enthalten.

Der Check wird nicht „grün“, obwohl der Job läuft. Prüfe, ob der curl-Ping wirklich ausgeführt wird und die richtige UUID trifft: curl -v https://DEINE_DOMAIN/ping/UUID sollte OK zurückgeben. Auf der Detailseite siehst du im Protokoll, ob und von welcher IP Pings ankommen.

Ich bekomme keine Benachrichtigung bei „down“. Es ist kein Integrations-Kanal zugewiesen, oder (bei E-Mail) fehlen die SMTP-Einstellungen. Unter „Integrations“ einen Kanal einrichten und dem Check zuweisen.

Wartung & Backups

  • Updates. Setze den Image-Tag (healthchecks/healthchecks:v4.4) gelegentlich auf die aktuelle Version und docker compose up -d; die Datenbank migriert beim Start automatisch. Den Rest übernimmt dein normaler Update-Prozess.
  • Backup. Der gesamte Zustand liegt in der SQLite-Datei unter data/ – ins Restic-Backup aufnehmen. Kleiner, aber feiner Nebeneffekt: Healthchecks überwacht dann das Backup, das es selbst mitsichert – schließe den Kreis, indem der Backup-Job auch einen Healthchecks-Check pingt.
  • Wer überwacht den Wächter? Healthchecks selbst sollte laufen, wenn es alarmieren soll. Ergänze es deshalb in Uptime Kuma als HTTP-Monitor – so decken sich die beiden Werkzeuge gegenseitig ab: Kuma prüft, dass Healthchecks erreichbar ist, Healthchecks prüft, dass deine Jobs gelaufen sind.

Feedback per E-Mail: feedback@serverkueche.de

Das könnte dir auch schmecken