<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Healthchecks – Serverküche</title><link>https://serverkueche.de/tags/healthchecks/</link><description>Healthchecks – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>de-DE</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Tue, 15 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/tags/healthchecks/index.xml" rel="self" type="application/rss+xml"/><item><title>Healthchecks: Cron-Jobs &amp; Backups überwachen (Dead-Man-Switch)</title><link>https://serverkueche.de/tutorials/healthchecks-jobs-ueberwachen/</link><pubDate>Tue, 15 Sep 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/tutorials/healthchecks-jobs-ueberwachen/</guid><description>Merkt, wenn ein Backup NICHT läuft: Healthchecks überwacht Cron-Jobs per Dead-Man-Switch und alarmiert bei Ausfall – selbst gehostet hinter Traefik.</description><content:encoded><![CDATA[<p>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. <a href="/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a> sagt dir, wenn ein Dienst <em>da</em> ist. Healthchecks sagt dir, wenn ein Job <em>nicht gelaufen</em> ist. Genau das ist der Unterschied, der Daten rettet.</p>
<h2 id="was-bauen-wir">Was bauen wir?</h2>
<p>Einen selbst gehosteten <strong>Healthchecks</strong>-Server (v4.4) hinter <a href="/tutorials/reverse-proxy-traefik/">Traefik</a>, der als <strong>Dead-Man-Switch</strong> 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 <a href="/tutorials/backups-mit-restic/">Restic-Backups</a>, Datenbank-Dumps und jeden anderen wiederkehrenden Job – und wirst benachrichtigt, <em>bevor</em> das Fehlen auffällt.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li>Ein Server mit laufendem <a href="/tutorials/reverse-proxy-traefik/">Traefik</a> und <a href="/tutorials/docker-compose-grundlagen/">Docker Compose</a></li>
<li>Eine <a href="/tutorials/domain-mit-server-verbinden/">Subdomain, die auf den Server zeigt</a> – im Folgenden <code>DEINE_DOMAIN</code></li>
<li>Wiederkehrende Jobs, die es zu überwachen lohnt (z. B. deine <a href="/tutorials/backups-mit-restic/">Restic-Backups</a>)</li>
</ul>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20"
     data-track-content data-content-name="Affiliate-Box · /tutorials/healthchecks-jobs-ueberwachen/" data-content-piece="VPS 1000 G12.5">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Empfehlung</span>
    <span title="Mit * markierte Links sind Affiliate-Links.">Anzeige</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12.5</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCore · 8 GB RAM · 128 GB SSD</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">ab 14,50 €/Monat</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Healthchecks ist genügsam und läuft gut neben deinem übrigen Stack.</p>
    </div>
    <a href="https://www.netcup.com/de/server/vps/vps-1000-g12.5-24m-eu?ref=44083#vps-1000-g12.5-12m-eu" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 1000 G12.5 · {page}"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Zu netcup →
</a>

  </div><div class="px-4 pb-4"><div class="not-prose my-3 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <p class="flex flex-wrap items-center gap-x-2 gap-y-1">
    <span>💶 <strong>5 € Gutschein</strong> für netcup-Neukunden:</span>
    <button type="button" data-voucher-code data-track-voucher="36nc17844976032"
            title="Zum Kopieren klicken" class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</button>
    <span class="text-xs text-slate-500 dark:text-slate-400">(nicht für Domains und VPS Lite)</span>
  </p>
  <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
    <a href="https://www.netcup.com/de/checkout/warenkorb?ref=44083" rel="sponsored noopener" target="_blank"
       data-track-event="Affiliate|netcup: Gutschein einlösen|5 € · {page}"
       class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Im Warenkorb einlösen →</a>
  </p>
</div></div>
</div>

<h2 id="schritt-für-schritt">Schritt für Schritt</h2>
<h3 id="schritt-1-das-prinzip-verstehen--ein-umgedrehtes-monitoring">Schritt 1: Das Prinzip verstehen – ein umgedrehtes Monitoring</h3>
<p>Klassisches Monitoring fragt aktiv: „Antwortet der Dienst?“ Healthchecks dreht das um: Der <strong>Job</strong> meldet sich beim Server. Jeder Check hat eine eindeutige <strong>Ping-URL</strong>. Nach erfolgreichem Lauf ruft der Job diese URL auf. Healthchecks erwartet den Ping in einem festgelegten Zeitfenster (<strong>Period</strong>) plus einer Toleranz (<strong>Grace Time</strong>). Kommt der Ping nicht rechtzeitig, gilt der Check als „down“ und Healthchecks alarmiert. Das ist der <strong>Dead-Man-Switch</strong>: Nicht das Vorhandensein eines Signals löst Alarm aus, sondern sein Ausbleiben.</p>
<h3 id="schritt-2-compose-datei-anlegen">Schritt 2: Compose-Datei anlegen</h3>
<p>Healthchecks ist eine Django-Anwendung; wir betreiben sie mit SQLite – für den typischen Selfhosting-Umfang völlig ausreichend. Lege das Projekt an:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /opt/healthchecks/data <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /opt/healthchecks
</span></span><span class="line"><span class="cl">chown -R 1000:1000 data</span></span></code></pre></div>
</div>
<p>Die <code>compose.yaml</code> – ersetze <code>DEINE_DOMAIN</code> und erzeuge einen eigenen <code>SECRET_KEY</code> (<code>openssl rand -hex 32</code>):</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">healthchecks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">healthchecks/healthchecks:v4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1000:1000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SITE_ROOT</span><span class="p">:</span><span class="w"> </span><span class="l">https://DEINE_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SITE_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">Serverkueche Healthchecks</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ALLOWED_HOSTS</span><span class="p">:</span><span class="w"> </span><span class="l">DEINE_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">CSRF_TRUSTED_ORIGINS</span><span class="p">:</span><span class="w"> </span><span class="l">https://DEINE_DOMAIN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DEBUG</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DB</span><span class="p">:</span><span class="w"> </span><span class="l">sqlite</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">/data/hc.sqlite</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SECRET_KEY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;DEIN_ZUFAELLIGER_SCHLUESSEL&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.enable=true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.hc.rule=Host(`DEINE_DOMAIN`)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.hc.entrypoints=websecure&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.routers.hc.tls.certresolver=le&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;traefik.http.services.hc.loadbalancer.server.port=8000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Zwei Fallen: DB_NAME und keine Umlaute in SITE_NAME
  </p>
  <div class="prose-kitchen text-sm">Bei SQLite ist <strong><code>DB_NAME: /data/hc.sqlite</code> Pflicht</strong> – ohne den Pfad legt Healthchecks die Datenbank an einer nicht beschreibbaren Stelle an und startet mit „unable to open database file“ nicht. Und: Schreibe <strong><code>SITE_NAME</code> rein in ASCII</strong> (kein „ü“, „ö“ …). Ein Umlaut in dieser Umgebungsvariable führt bei aktuellem Python zu einem <code>UnicodeEncodeError: surrogates not allowed</code> – die Seite quittiert dann mit HTTP 500 (genau das ist mir beim Testen dieses Tutorials passiert).</div>
</div>
<h3 id="schritt-3-starten-und-anmelden">Schritt 3: Starten und anmelden</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d</span></span></code></pre></div>
</div>
<p>Das Image bringt einen Healthcheck mit; Traefik leitet erst weiter, wenn der Container <code>healthy</code> ist (etwa 20–30 Sekunden). Prüfe:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose ps</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME                          IMAGE                        STATUS
</span></span><span class="line"><span class="cl">healthchecks-healthchecks-1   healthchecks/healthchecks:v4.4  Up (healthy)</span></span></code></pre></div>
</div>
<p>Ein Konto bringt Healthchecks <strong>nicht</strong> von selbst mit – das Image legt beim Start nur die
Datenbank an. Leg deinen Zugang deshalb einmalig selbst an (Passwort ersetzen):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose <span class="nb">exec</span> healthchecks python manage.py createsuperuser <span class="se">\
</span></span></span><span class="line"><span class="cl">  --email admin@DEINE_DOMAIN --password DEIN_STARKES_PASSWORT</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Superuser created successfully.</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>SUPERUSER_EMAIL wirkt nicht
  </p>
  <div class="prose-kitchen text-sm">In vielen Anleitungen stehen <code>SUPERUSER_EMAIL</code> und <code>SUPERUSER_PASSWORD</code> als Umgebungsvariablen
in der Compose-Datei. Das offizielle Image <strong>liest sie nicht</strong> – sein Start-Hook führt
ausschließlich <code>manage.py migrate</code> aus. Wer sich darauf verlässt, steht vor einer Anmeldeseite,
an der kein Konto existiert (getestet mit v4.3 <strong>und</strong> v4.4). Der Weg über <code>createsuperuser</code> oben
ist der verlässliche.</div>
</div>
<p>Öffne jetzt <code>https://DEINE_DOMAIN/</code> und melde dich mit diesen Zugangsdaten an:</p>
<p><figure class="my-6"><img src="/tutorials/healthchecks-jobs-ueberwachen/hc-login_hu_b70ec50f1376f78d.webp" srcset="/tutorials/healthchecks-jobs-ueberwachen/hc-login_hu_50c18b3066470d62.webp 480w, /tutorials/healthchecks-jobs-ueberwachen/hc-login_hu_b70ec50f1376f78d.webp 768w, /tutorials/healthchecks-jobs-ueberwachen/hc-login_hu_b57cdf37ed1dbbfd.webp 1200w, /tutorials/healthchecks-jobs-ueberwachen/hc-login_hu_acedcfe6d6568f0b.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/healthchecks-jobs-ueberwachen/hc-login_hu_68b55ee4cd86bb76.webp"
    alt="Die Anmeldeseite von Healthchecks unter der eigenen HTTPS-Domain" title="Der selbst gehostete Healthchecks-Login – das Konto stammt aus dem createsuperuser-Aufruf"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Der selbst gehostete Healthchecks-Login – das Konto stammt aus dem createsuperuser-Aufruf</figcaption></figure></p>
<h3 id="schritt-4-ein-projekt-und-den-ersten-check-anlegen">Schritt 4: Ein Projekt und den ersten Check anlegen</h3>
<p>Nach dem Login legst du über <strong>New Project…</strong> ein Projekt an (z. B. „Serverküche“) und darin über <strong>Add Check</strong> deinen ersten Check. Gib ihm einen sprechenden Namen, Tags und einen Zeitplan – <strong>Period</strong> = erwarteter Abstand zwischen zwei Läufen (für ein tägliches Backup: 1 Tag), <strong>Grace Time</strong> = 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:</p>
<p><figure class="my-6"><img src="/tutorials/healthchecks-jobs-ueberwachen/hc-checks_hu_d32b5bde7768acf2.webp" srcset="/tutorials/healthchecks-jobs-ueberwachen/hc-checks_hu_afe3b1bc56c24cd4.webp 480w, /tutorials/healthchecks-jobs-ueberwachen/hc-checks_hu_d32b5bde7768acf2.webp 768w, /tutorials/healthchecks-jobs-ueberwachen/hc-checks_hu_dbc104bd7adacb8b.webp 1200w, /tutorials/healthchecks-jobs-ueberwachen/hc-checks_hu_bfc1fb2a3b9fcc73.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/healthchecks-jobs-ueberwachen/hc-checks_hu_7b886e8381e7fe59.webp"
    alt="Die Healthchecks-Übersicht mit mehreren Checks, Ping-URLs und Status-Anzeige" title="Die Check-Übersicht: grüner Haken für „läuft“, dazu Ping-URL, Zeitplan und letzter Ping"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Die Check-Übersicht: grüner Haken für „läuft“, dazu Ping-URL, Zeitplan und letzter Ping</figcaption></figure></p>
<p>Jeder Check bekommt eine eigene <strong>Ping-URL</strong> der Form <code>https://DEINE_DOMAIN/ping/&lt;UUID&gt;</code>. Ein Klick auf den Check öffnet die Detailseite mit Anleitung, Verlauf und Status:</p>
<p><figure class="my-6"><img src="/tutorials/healthchecks-jobs-ueberwachen/hc-detail_hu_e13246aedbbf057b.webp" srcset="/tutorials/healthchecks-jobs-ueberwachen/hc-detail_hu_2424adeceaa1d5b2.webp 480w, /tutorials/healthchecks-jobs-ueberwachen/hc-detail_hu_e13246aedbbf057b.webp 768w, /tutorials/healthchecks-jobs-ueberwachen/hc-detail_hu_e8a6e7129b4b2ea4.webp 1200w, /tutorials/healthchecks-jobs-ueberwachen/hc-detail_hu_aed8185972eebfb.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/tutorials/healthchecks-jobs-ueberwachen/hc-detail_hu_50e0ad1aab93dcf6.webp"
    alt="Die Detailseite eines Checks mit Ping-URL, aktuellem Status „up“ und Ereignisprotokoll" title="Die Detailseite: Ping-URL, aktueller Status und das Protokoll der eingegangenen Pings"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Die Detailseite: Ping-URL, aktueller Status und das Protokoll der eingegangenen Pings</figcaption></figure></p>
<h3 id="schritt-5-einen-job-den-ping-senden-lassen">Schritt 5: Einen Job den Ping senden lassen</h3>
<p>Jetzt der Kern. Deinen Job lässt du nach erfolgreichem Lauf die Ping-URL aufrufen. Das einfachste Beispiel – am Ende deines Skripts:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsS -m <span class="m">10</span> --retry <span class="m">5</span> https://DEINE_DOMAIN/ping/DEINE_CHECK_UUID</span></span></code></pre></div>
</div>
<p><code>-fsS</code> macht curl leise, aber meldet Fehler; <code>-m 10</code> bricht nach 10 Sekunden ab; <code>--retry 5</code> fängt kurze Netzwerkaussetzer ab. Noch besser: den <strong>Exit-Code</strong> des Jobs mitmelden, damit ein <em>fehlgeschlagener</em> Lauf sofort als Fehler erscheint statt als „kein Ping“:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nv">URL</span><span class="o">=</span><span class="s2">&#34;https://DEINE_DOMAIN/ping/DEINE_CHECK_UUID&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ... hier läuft dein eigentlicher Job ...</span>
</span></span><span class="line"><span class="cl">restic backup /wichtige/daten
</span></span><span class="line"><span class="cl"><span class="c1"># Exit-Code an Healthchecks melden (0 = ok, sonst Fehler)</span>
</span></span><span class="line"><span class="cl">curl -fsS -m <span class="m">10</span> --retry <span class="m">5</span> <span class="s2">&#34;</span><span class="nv">$URL</span><span class="s2">/</span><span class="nv">$?</span><span class="s2">&#34;</span></span></span></code></pre></div>
</div>
<h3 id="schritt-6-restic-backups-überwachen">Schritt 6: Restic-Backups überwachen</h3>
<p>Das ist der Paradefall. Wenn deine <a href="/tutorials/backups-mit-restic/">Restic-Backups</a> per <a href="/tutorials/systemd-grundlagen/">systemd-Timer</a> 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.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Start- und Fehlersignale nutzen
  </p>
  <div class="prose-kitchen text-sm">Healthchecks kann mehr als „fertig“: Ein Ping auf <code>.../ping/UUID/start</code> <strong>vor</strong> dem Job misst zusätzlich die Laufzeit, ein Ping auf <code>.../ping/UUID/fail</code> meldet aktiv einen Fehlschlag. So siehst du nicht nur <em>ob</em>, sondern auch <em>wie lange</em> ein Job lief – nützlich, um schleichend langsamer werdende Backups zu erkennen.</div>
</div>
<h3 id="schritt-7-benachrichtigungen-einrichten">Schritt 7: Benachrichtigungen einrichten</h3>
<p>Ein Alarm nützt nur, wenn er dich erreicht. Unter <strong>Integrations</strong> verbindest du Kanäle: E-Mail (dafür SMTP-Umgebungsvariablen setzen), <a href="/tutorials/ntfy-push-benachrichtigungen/">ntfy</a>, 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.</p>
<h2 id="wenn-es-nicht-funktioniert">Wenn es nicht funktioniert</h2>
<div class="troubleshoot not-prose">
<p><strong>Container startet nicht: „unable to open database file“.</strong> <code>DB_NAME: /data/hc.sqlite</code> fehlt oder der <code>data</code>-Ordner gehört nicht UID 1000. Pfad setzen und <code>chown -R 1000:1000 /opt/healthchecks/data</code> (siehe Warnung in Schritt 2).</p>
<p><strong>Die Seite antwortet mit HTTP 500.</strong> Häufigste Ursache: ein Umlaut in <code>SITE_NAME</code> (oder einer anderen Text-Umgebungsvariable). Auf reines ASCII umstellen und neu starten. Zur Diagnose vorübergehend <code>DEBUG: &quot;True&quot;</code> setzen – aber danach wieder auf <code>False</code>.</p>
<p><strong>Login schlägt fehl / CSRF-Fehler beim Absenden.</strong> <code>CSRF_TRUSTED_ORIGINS: https://DEINE_DOMAIN</code> muss gesetzt sein – Django lehnt sonst POST-Anfragen hinter dem Reverse Proxy ab. Und <code>ALLOWED_HOSTS</code> muss exakt deine Domain enthalten.</p>
<p><strong>Der Check wird nicht „grün“, obwohl der Job läuft.</strong> Prüfe, ob der <code>curl</code>-Ping wirklich ausgeführt wird und die richtige UUID trifft: <code>curl -v https://DEINE_DOMAIN/ping/UUID</code> sollte <code>OK</code> zurückgeben. Auf der Detailseite siehst du im Protokoll, ob und von welcher IP Pings ankommen.</p>
<p><strong>Ich bekomme keine Benachrichtigung bei „down“.</strong> Es ist kein Integrations-Kanal zugewiesen, oder (bei E-Mail) fehlen die SMTP-Einstellungen. Unter „Integrations“ einen Kanal einrichten und dem Check zuweisen.</p>

</div>

<h2 id="wartung--backups">Wartung &amp; Backups</h2>
<ul>
<li><strong>Updates.</strong> Setze den Image-Tag (<code>healthchecks/healthchecks:v4.4</code>) gelegentlich auf die aktuelle Version und <code>docker compose up -d</code>; die Datenbank migriert beim Start automatisch. Den Rest übernimmt dein normaler <a href="/tutorials/docker-stack-aktuell-halten/">Update-Prozess</a>.</li>
<li><strong>Backup.</strong> Der gesamte Zustand liegt in der SQLite-Datei unter <code>data/</code> – ins <a href="/tutorials/backups-mit-restic/">Restic-Backup</a> 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.</li>
<li><strong>Wer überwacht den Wächter?</strong> Healthchecks selbst sollte laufen, wenn es alarmieren soll. Ergänze es deshalb in <a href="/tutorials/uptime-kuma-monitoring/">Uptime Kuma</a> als HTTP-Monitor – so decken sich die beiden Werkzeuge gegenseitig ab: Kuma prüft, dass Healthchecks <em>erreichbar</em> ist, Healthchecks prüft, dass deine Jobs <em>gelaufen</em> sind.</li>
</ul>
]]></content:encoded></item></channel></rss>