DarkWolfCave
linux

CronWolf Behind the Scenes: Was alles schiefging

Server-Racks mit leuchtenden Kabeln und einem Wolf-Logo - Architektur von CronWolf
DarkWolf im Hoodie am Laptop KI-Bild Generiert mit Gemini

Wer ein Monitoring-Tool betreibt, kann sich selbst keine Ausfallzeiten leisten. Oder?

CronWolf überwacht Cronjobs und Server anderer Leute. Trotzdem sind in den ersten sechs Monaten Backups unbemerkt ausgefallen, ein Deployment hat die Production für 15 Minuten lahmgelegt und derselbe Nginx-Bug ist zweimal aufgetreten. Hier ist die ungefilterte Geschichte - mit allem was schiefging und was ich daraus gelernt habe.

DarkWolfCave.de

Wie alles anfing - und wo es heute steht

CronWolf entstand aus einem konkreten Problem: Meine Raspberry Pi Backups schlugen regelmäßig fehl, ohne dass ich es mitbekam. Der Cronjob lief, das Skript startete, aber der Dump war leer oder der Upload hing - und niemand hat nachgeschaut.

Die erste Version war ein einfaches Frontend und Backend in einem Docker-Compose-File. Heute läuft CronWolf auf einem dedizierten Linux-VPS mit Debian, PostgreSQL, Nginx, Fail2ban, WireGuard und aktuell 14 Docker-Containern. Dazu gehören Production- und Staging-Umgebungen, eine Umami-Instanz für datenschutzfreundliche Analytics und ein SSL-Aggregator der die Zertifikate aller Wolf-Empire-Domains überwacht.

Die Architektur ist über mehrere Iterationen gewachsen - jede davon ausgelöst durch ein konkretes Problem, nicht durch vorausschauende Planung. Dieser Artikel zeigt, welche Fehler dahinterstecken.

VIP Support
Wolf Support Avatar

Du wirst hier einen groben Überblick finden.
Allerdings biete ich dir auch noch etwas mehr Support an:

  • Du benötigst persönlichen Support
  • Du möchtest von Beginn an Unterstützung bei deinem Projekt
  • Du möchtest ein hier vorgestelltes Plugin durch mich installieren und einrichten lassen
  • Du würdest gerne ein von mir erstelltes Script etwas mehr an deine Bedürfnisse anpassen

Für diese Punkte und noch einiges mehr habe ich einen limitierten VIP-Tarif eingerichtet.

Falls der Tarif gerade nicht verfügbar ist, kontaktiere mich auf Discord!

15 Minuten Production-Downtime - mein erster Blue-Green Versuch

Im November 2025 war klar: Das Deployment-Verfahren muss sich ändern. Bis dahin lief jedes Update so: Container stoppen, Image neu bauen, Container starten, Migrations ausführen, Static Files sammeln. Dauer: 2-5 Minuten. In dieser Zeit war CronWolf komplett offline.

Das klingt nicht dramatisch - bis ein User fragt: “Ist es professionell, dass meine Status-Page während deiner Deployments Outages anzeigt?” Die Antwort ist natürlich nein. Schlimmer noch: Wenn User sich an gelegentliche kurze Ausfälle gewöhnen, ignorieren sie irgendwann echte Probleme.

Die Idee war einfach: Blue-Green Deployment. Zwei identische Container-Sets, Nginx routet den Traffic, beim Deploy wird die inaktive Seite aktualisiert und umgeschaltet. Zero Downtime.

Die Umsetzung war katastrophal. Ich habe direkt auf Production deployed, ohne lokalen Test, ohne Staging. Was passierte:

  1. Die .env-Datei hatte CONTAINER_PREFIX=myapp_ (mit Underscore am Ende). Docker Compose hängt den Service-Namen mit einem weiteren Underscore an - daraus wurde myapp__backend_blue mit doppeltem Underscore. Nginx suchte aber myapp_backend_blue mit einfachem Underscore.
  2. Nginx konnte keinen der neuen Container finden. 502 Bad Gateway.
  3. Die Nginx-Config lag als Read-Only Bind Mount im Container - ich konnte sie nicht mal schnell im Container korrigieren.

Ergebnis: 15 Minuten Production-Downtime. Bei einem Monitoring-Tool. Weil ich zu ungeduldig war, vorher auf Staging zu testen.

Die Migration hat letztendlich funktioniert - mit einem Sicherheitsnetz: Die alten Container blieben als Nginx-Backup konfiguriert, die neuen (Green) wurden parallel gestartet, und erst nach bestätigtem Health-Check wurde umgeschaltet. Die richtige Reihenfolge: Erst Container starten, dann Nginx umschalten. Niemals umgekehrt.

Du willst Docker & Infrastruktur nicht selbst einrichten? Ich übernehme das für dich — schau dir meine Services an

Schwarzer Bildschirm - zwei Bugs, ein Symptom

In den Wochen nach der Blue-Green-Migration gab es zweimal schwarze Bildschirme bei Usern. Gleiches Symptom, komplett unterschiedliche Ursachen.

Bug 1: Alter Cache, neuer Container. Noch vor Blue-Green, beim alten Single-Container-Deployment. Nach einem Frontend-Update bekamen User auf bestimmten Seiten einen schwarzen Bildschirm - aber nur auf Pages die durch die Backend-Middleware geroutet wurden (/pricing, /docs, /vorlagen). Die Homepage ging. Das Deploy-Skript löschte den Middleware-Cache sofort nach dem Container-Start, aber der neue Container war noch nicht komplett hochgefahren. Die Middleware holte sich das alte HTML aus dem Build-Cache des Containers, cached eine Referenz auf index-Bbj9_1py.js (alt) statt index-Crovxepu.js (neu) - und die alte Datei gab es nicht mehr.

Der erste Fix war ein sleep 10 vor dem Cache-Clear. Nicht schön, aber es funktionierte. Inzwischen wartet das Deploy-Skript auf den Docker Health-Check, wärmt den Cache dann aktiv mit Requests auf alle kritischen URLs auf und löscht erst danach den alten Cache. Kein sleep mehr nötig.

Bug 2: Alte Container weg, alte Assets weg. Nach der Blue-Green-Migration stoppte das Deploy-Skript die alten Container nach dem Umschalten. Aber User mit aktiven Sessions hatten noch JS-Dateien der alten Version im Browser. React Lazy Loading versuchte, alte Chunks nachzuladen. Container weg, Assets 404, schwarzer Bildschirm.

Die Lösung: Beide Farben laufen permanent. Nginx macht Load-Balancing zwischen Blue und Green, beim Deploy wird eine Farbe aktualisiert. Alte Assets bleiben über die andere Farbe erreichbar.

Discord-Alarmsturm nach der Migration

Direkt nach der Blue-Green-Umstellung passierte noch etwas Lustiges: Das Docker-Monitoring-Skript schickte einen Sturm von Discord-Alerts - “Container Down - PRODUCTION”. Alle paar Minuten, eins nach dem anderen.

Die Ursache war harmlos: Das Skript suchte nach den alten Container-Namen und fand sie nicht. Die neuen Blue/Green-Container liefen einwandfrei, aber das Monitoring kannte ihre Namen nicht. Also: Alles lief, aber das Monitoring war überzeugt dass alles tot ist. Das Gegenteil vom Backup-Problem - dort lief nichts und das Monitoring sagte “alles okay”.

Der Nginx-DNS-Bug der zweimal kam

Im Januar 2026 meldete das Monitoring: CronWolf 502. Alle User betroffen.

Die Ursache: Nginx löst Upstream-Hostnamen beim Start auf und cached die IP-Adressen. Wenn Docker einem Container beim Deploy eine neue IP zuweist, routet Nginx weiter zur alten IP. Connection refused. 502.

Der Fix war schnell gefunden: resolver 127.0.0.11 in der Nginx-Config für dynamische DNS-Auflösung, plus ein nginx -s reload im Deploy-Skript nach jedem Container-Neustart.

Im April 2026 passierte exakt dasselbe nochmal. Der nginx -s reload stand am Ende des Deploy-Skripts, aber nicht direkt nach dem Container-Restart. Der Health-Check lief vor dem Reload, und weil der Health-Check selbst durch Nginx ging (Django’s SECURE_SSL_REDIRECT leitet auf HTTPS um, was durch Nginx läuft), schlug er fehl - obwohl der Container eigentlich gesund war. Die Lösung: Den Reload direkt nach dem docker compose up -d platzieren, vor dem Health-Check.

💡 Tipp: Wenn du Nginx als Reverse-Proxy vor Docker-Containern nutzt, setze resolver 127.0.0.11 valid=10s in deiner Upstream-Config. Docker’s interner DNS-Server sorgt dafür, dass Hostnamen immer zur aktuellen Container-IP aufgelöst werden. Ohne das cached Nginx beim Start - und jeder Container-Neustart bricht das Routing.

Drei Tage tote Backups - und niemand merkte es

Mitte November 2025, kurz nach der Blue-Green-Migration, stellte ich fest: Die Backups liefen seit drei Tagen nicht mehr. Kein Alarm, kein Hinweis - nichts.

Was passiert war: Durch die Blue-Green-Umstellung hatten sich die Container-Namen geändert. Das Backup-Skript suchte den alten Namen, fand keinen Container, und beendete sich ohne Fehlermeldung. Der Cronjob meldete “Success” an das System - weil das Skript mit Exit-Code 0 endete. Technisch kein Fehler. Praktisch kein Backup.

Dazu kam: Das Skript versuchte noch, eine Staging-Umgebung zu sichern die nicht mehr existierte. Und der Restore-Test meldete fälschlicherweise Erfolg bei leeren Daten.

Das war der Moment, in dem ich die Backup-Überwachung per CronWolf eingebaut habe. Das Skript sendet jetzt beim Start einen Ping an die CronWolf-API, bei Erfolg einen Success-Ping mit Statistiken (Dump-Größe, Tabellen-Anzahl), bei Fehler einen Fail-Ping. Bleibt der Ping aus, schlägt CronWolf Alarm.

#!/bin/bash
# Backup mit CronWolf-Monitoring (vereinfacht)

PING_UUID="DEINE-CHECK-UUID"

# Start signalisieren
curl -fsS --retry 3 "https://cronwolf.de/api/v2/ping/$PING_UUID/start" > /dev/null 2>&1

# PostgreSQL Dump erstellen
docker exec -i DB_CONTAINER pg_dump -U DB_USER DB_NAME > /backup/dump_$(date +%Y%m%d).sql

if [ $? -eq 0 ]; then
    curl -fsS --retry 3 "https://cronwolf.de/api/v2/ping/$PING_UUID/" > /dev/null 2>&1
else
    curl -fsS --retry 3 "https://cronwolf.de/api/v2/ping/$PING_UUID/fail" > /dev/null 2>&1
fi

Seitdem hat die Überwachung drei weitere Probleme aufgedeckt, bevor sie beim nächsten Restore aufgefallen wären. Unter anderem einen hängenden pg_dump und einen vollen NAS-Mount.

Fail2ban, Endlessh und Container Hardening

Die Absicherung hat sich über Monate aufgebaut. Aktuell sieht das so aus:

Netzwerk-Ebene: Alle administrativen Zugriffe laufen über WireGuard-VPN. Datenbank-Ports, Docker-Sockets, alles was nicht öffentlich sein muss, ist von außen nicht erreichbar. Endlessh läuft als SSH-Tarpit auf Port 22 und beschäftigt automatisierte Scanner, während der echte SSH-Dienst auf einem anderen Port läuft.

Anwendungs-Ebene: Fail2ban mit anwendungsspezifischen Jails schützt vor Brute-Force und bösartigen Crawlern. Ein Punkt der gerne übersehen wird:

# /etc/fail2ban/jail.d/app-auth.conf
[app-auth]
enabled  = true
filter   = app-auth
chain    = DOCKER-USER
logpath  = /var/log/nginx/app-error.log
maxretry = 5
findtime = 600
bantime  = 3600

Die Zeile chain = DOCKER-USER ist entscheidend. Docker-Ports die per -p exponiert werden, umgehen die INPUT-Chain komplett und laufen über FORWARD. Standard-Fail2ban-Bans landen in INPUT und greifen bei Docker-Containern nicht. Erst mit DOCKER-USER wird der Traffic vor dem Docker-Routing gefiltert.

Container-Ebene: Im März 2026 habe ich alle 15 Container gehärtet: cap_drop: ALL entfernt sämtliche Linux Capabilities, read_only: true macht das Dateisystem schreibgeschützt, security_opt: no-new-privileges:true verhindert Privilege Escalation. Jeder Container bekommt nur die Capabilities zurück, die er tatsächlich braucht - PostgreSQL zum Beispiel CHOWN, SETUID, SETGID und FOWNER, die Python-Backend-Container gar keine.

Das gesamte Setup wird über Ansible verwaltet und per Server-Härtung abgesichert, damit die Konfiguration reproduzierbar bleibt und nicht mit der Zeit driftet.

Warum Nginx und nicht Traefik

Eine Frage die bei Docker-Setups oft kommt: Warum nicht Traefik? CronWolf hatte von Anfang an Nginx - Traefik stand nie zur Debatte. Bei einem Docker-Compose Setup auf einem einzelnen VPS ist Nginx schlicht der einfachere Ansatz: Config-Dateien liegen git-tracked im Repo, Rate-Limiting und Security Headers sind direkt konfigurierbar, und für Blue-Green Deployments definiere ich Upstreams explizit und lade sie per nginx -s reload neu.

Traefik hat seine Stärken bei automatischem Service Discovery und Let’s-Encrypt-Integration - bei anderen Projekten nutze ich es auch. Aber für CronWolf mit seinen mehreren Domains (Production, Staging, Analytics) und den Blue-Green Upstreams wäre die Label-basierte Konfiguration nicht simpler gewesen als eine klassische Nginx-Config.

Was ich nach sechs Monaten anders sehe

Staging ist kein Nice-to-have. Die Staging-Umgebung war lange ein “deploye mal schnell”-System ohne Health-Checks. Seit sie dieselben Anforderungen erfüllt wie Production, sind Bugs aufgetaucht die sonst live gegangen wären. Der 15-Minuten-Ausfall beim Blue-Green-Versuch wäre auf Staging passiert statt auf Production.

Monitoring des Monitorings klingt wie ein Witz. Ist es nicht. Die Tatsache, dass Backups drei Tage lang still sterben konnten, hat mich mehr beschäftigt als jeder andere Bug. Inzwischen überwacht CronWolf sich selbst - und das ist der wichtigste Teil des gesamten Setups.

Derselbe Bug kommt zweimal. Der Nginx-DNS-Cache-Bug im Januar war gefixt. Im April kam er in leicht anderer Form wieder, weil der Fix an der falschen Stelle im Skript stand. Die Lektion: Ein Fix ist nur dann vollständig, wenn er an der richtigen Stelle im Ablauf sitzt - nicht nur im Code.

Container Hardening kostet einen Nachmittag und bringt viel. 15 Container auf Read-Only, Cap-Drop-All und No-New-Privileges umzustellen hat ein paar Stunden gedauert - hauptsächlich weil man für jeden Service herausfinden muss, welche tmpfs-Mounts er braucht. Aber der Sicherheitsgewinn ist real: Selbst wenn ein Angreifer in einen Container kommt, kann er weder Dateien schreiben noch Rechte eskalieren.

Wer seine eigenen Cronjobs oder Backups überwachen möchte: CronWolf ist kostenlos nutzbar.

FAQ - Frequently Asked Questions DarkWolfCave
DarkWolf hilft bei FAQs

Häufig gestellte Fragen

Wie funktioniert Blue-Green Deployment mit Docker und Nginx?
Zwei identische Container-Sets (Blue und Green) laufen parallel hinter einem Nginx-Reverse-Proxy. Beim Deploy wird die inaktive Farbe aktualisiert und nach bestandenem Health-Check per nginx -s reload aktiviert. Der Traffic wechselt ohne Unterbrechung.
Was passiert wenn ein Blue-Green Deployment fehlschlägt?
Im besten Fall bleibt die alte Farbe aktiv und User merken nichts. Im schlechtesten Fall - wie bei CronWolf am Anfang - zeigt Nginx 502 Bad Gateway, weil die Config auf Container zeigt die noch nicht laufen. Daher immer erst Container starten, dann Nginx umschalten.
Warum cached Nginx alte Docker-Container-IPs?
Nginx löst Upstream-Hostnamen beim Start oder Reload auf und cached die IP-Adressen. Wenn Docker einem Container bei einem Neustart eine neue IP zuweist, routet Nginx weiter zur alten IP. Die Lösung: resolver 127.0.0.11 in der Nginx-Config und ein Reload nach jedem Container-Restart.
Wie überwacht man Backups mit CronWolf?
Das Backup-Skript sendet beim Start einen Ping an die CronWolf-API, bei Erfolg einen Success-Ping und bei Fehler einen Fail-Ping. CronWolf misst die Laufzeit und alarmiert wenn das Backup ausbleibt oder ungewöhnlich lange dauert.
Was ist Container Hardening mit Docker?
Container Hardening beschränkt die Rechte eines Containers auf das absolute Minimum: cap_drop ALL entfernt alle Linux Capabilities, read_only macht das Dateisystem schreibgeschützt, und no-new-privileges verhindert Privilege Escalation. Nur explizit benötigte Capabilities werden einzeln zurückgegeben.

Kommentare

URLs werden automatisch verlinkt
Kommentare werden geladen...