Du baust eine Self-Healing Monitoring Pipeline: Zabbix erkennt Probleme, n8n routet und versucht automatisch zu fixen, und bei Fehlschlag eskaliert eine KI die Diagnose per Telegram. Klingt nach der Zukunft.

Realität: 852 Alerts empfangen, 0 mal repariert. Kein Telegram. Keine Diagnose. Nur stille Datenbankeinträge.

Hier ist die Geschichte, wie ich das debuggt habe — und warum die beste Lösung am Ende kein vollautonomer Bot war, sondern ein KI-Assistent der vorschlägt und auf dein OK wartet.

Die Architektur (auf dem Papier)

Zabbix Trigger
  → Wrapper-Script (bash)
    → n8n Alert Router (Webhook)
      → Parse, Route, Log
      → Remediation Engine (SSH-Fix)
      → Bei Fehlschlag: KI-Diagnose
        → Telegram: "Fix vorschlagen"

Drei Layer. Jeder einzelne war kaputt.

Bug 1: Das Henne-Ei-Problem

Das Wrapper-Script smart-notify.sh im Zabbix-Container schickte Alerts an:

WEBHOOK_URL="https://flow.darkside.local/webhook/zabbix-alert"

Diese URL geht über den Reverse Proxy (nginx auf einem separaten Host). Wenn nginx down ist, feuert Zabbix einen Alert — und genau dieser Alert kann n8n nicht erreichen, weil der Weg über nginx läuft.

Das Script hatte einen Telegram-Fallback:

if [ "$http_code" != "200" ]; then
  # Direkt per Telegram API senden
  telegram_text="⚠️ [n8n bypass] ${SUBJECT} ${BODY}"
  curl ... "https://api.telegram.org/bot${TOKEN}/sendMessage"
fi

Symptom: Jeder zweite Alert kam als [n8n bypass] in Telegram an. n8n hat ihn nie gesehen.

Fix: Internes Routing als Primary, externes als Fallback:

WEBHOOK_URL_INTERNAL="http://192.168.66.10:8098/webhook/zabbix-alert"
WEBHOOK_URL_EXTERNAL="https://flow.darkside.local/webhook/zabbix-alert"

# Versuch 1: intern (kein Reverse Proxy nötig)
http_code=$(curl ... "$WEBHOOK_URL_INTERNAL")
[ "$http_code" = "200" ] && exit 0

# Versuch 2: extern
http_code=$(curl ... "$WEBHOOK_URL_EXTERNAL")
[ "$http_code" = "200" ] && exit 0

# Versuch 3: Telegram direkt

Lesson: Monitoring-Webhooks sollten nie über die Infrastruktur routen, die sie überwachen.

Bug 2: Der Parser verstand kein Zabbix

Der n8n Alert Router hatte einen Code Node der Zabbix-Nachrichten parste:

trigger_name = extractLine(body, 'Trigger:');
event_id = extractLine(body, 'Event ID:');
if (subject.startsWith('PROBLEM:')) status = 'PROBLEM';

Aber Zabbix sendet:

Problem name: Todesstern-Kantine nicht erreichbar
Host: srv-r2d2
Severity: High
Original problem ID: 5703535

Drei Mismatches:

  • Trigger: → Zabbix sagt Problem name:
  • Event ID: → Zabbix sagt Original problem ID:
  • PROBLEM: (Großbuchstaben) → Zabbix sagt Problem: (mixed case)

Ergebnis: Jeder Alert wurde mit trigger_name: "", event_id: <timestamp>, status: UNKNOWN geloggt. Die Severity-basierte Routing-Logik funktionierte nie.

Fix:

trigger_name = extractLine(body, 'Problem name:', 'Trigger:');
event_id = extractLine(body, 'Original problem ID:', 'Event ID:');

const subjectLower = subject.toLowerCase();
if (subjectLower.startsWith('problem:') || subjectLower.startsWith('problem ')) {
  status = 'PROBLEM';
} else if (subjectLower.startsWith('resolved') || body.includes('has been resolved')) {
  status = 'RESOLVED';
}

extractLine akzeptiert jetzt mehrere Prefixes als Fallback-Kette.

Bug 3: Switch v3 Nodes crashen in n8n 2.x

Der Alert Router hatte zwei Switch-Nodes (v3) für Routing nach Suppress-Pattern und Severity. Beide crashten mit:

TypeError: Cannot read properties of undefined (reading 'push')

Switch v3 Nodes sind in n8n 2.13.3 inkompatibel mit der API-Validierung. Weder Import noch Aktivierung funktionierte. Switch v1 hatte den gleichen Fehler.

Fix: Beide Switch-Nodes durch einen einzigen If-Node ersetzt:

Severity >= 3 (Average)?
  → Ja: Telegram Escalation + KI-Diagnose
  → Nein: Telegram Info + DB-Log

Simpler, robuster, keine Node-Version-Probleme.

Bug 4: $json-Kontext geht nach Postgres verloren

Die Telegram-Node verwendete {{ $json.chat_id }} für den Empfänger. Aber nach dem Postgres INSERT sah $json so aus:

{"id": 873, "event_id": "5703535", "host": "srv-r2d2", "severity": 4, "status": "PROBLEM", "action": "pending"}

Kein chat_id — das steht nicht in der alert_log Tabelle. Kein severity_str — nur der numerische Wert.

Symptom: 400 - chat_id is empty

Fix: Chat-ID hardcoden (es geht sowieso immer an den gleichen Channel), Severity-Text aus dem numerischen Wert ableiten:

severity == 5 ? 'Disaster' : severity == 4 ? 'High' : severity == 3 ? 'Average' : 'Information'

Bug 5: If-Node hat vertauschte Outputs

n8n If v2 Node: Output 0 ist TRUE, Output 1 ist FALSE. Oder?

Nein. In der Praxis (n8n 2.13.3) war es umgekehrt. Ein PROBLEM-Alert mit status !== "RESOLVED" landete im TRUE-Branch (Mark Resolved) statt im FALSE-Branch (Route weiter).

Fix: Connections getauscht. Output 0 → Suppress Pattern, Output 1 → Mark Resolved.

Keine Doku dazu gefunden. Trial and Error.

Das eigentliche Problem: Vollautomatisch ist falsch

Nachdem alle Bugs gefixt waren, lief die Pipeline. n8n empfing Alerts, loggte sie, schickte Telegram. Aber die automatische Remediation (SSH auf den Host, Service neustarten) war kaputt — SSH-Keys nicht deployed, Playbooks nicht gematchet.

Und dann die Frage: Will ich das überhaupt vollautomatisch?

Nein. Ein Bot der eigenständig Services neustartet, Configs ändert oder Disk-Space aufräumt ist ein Risiko. Besonders wenn die KI-Diagnose falsch liegt.

Die bessere Architektur: Human-in-the-Loop

Zabbix Alert
  → n8n: Parse, Route, Log
    → Telegram: "🟠 HIGH - Todesstern-Kantine down"
    → Claude AI: Diagnostiziert per SSH
      → Telegram: "Root Cause: Container gestoppt (Exit 0)"
                   "Vorgeschlagener Fix: docker compose up -d"
                   "Risiko: Niedrig"
                   [✅ Fix ausführen] [❌ Ablehnen]

Die KI macht die Arbeit — SSH-Zugang, Log-Analyse, Root-Cause-Diagnose. Aber der Mensch entscheidet. Ein Knopfdruck auf dem Handy.

Implementation

Der Webhook-Endpunkt auf dem Claude-Server empfängt den Alert von n8n:

@app.router.add_post("/webhook/zabbix")
async def handle_zabbix_webhook(request):
    host = data.get("host")
    trigger = data.get("trigger_name")
    severity = data.get("severity")

    # Claude-Session starten
    asyncio.create_task(handler._start_diagnosis(alert, autonomy))

Claude diagnostiziert mit einem Prompt der explizit sagt:

WICHTIG: Führe KEINE Fixes durch! Nur diagnostizieren.
Liefere: 1) Root-Cause-Analyse 2) Konkreter Fix-Vorschlag 3) Risiko-Einschätzung

Nach der Diagnose: Approval-Buttons in Telegram:

buttons = [
    InlineKeyboardButton("✅ Fix ausführen", callback_data=f"approve:{conv_name}"),
    InlineKeyboardButton("❌ Ablehnen", callback_data=f"reject:{conv_name}"),
]

# Fix-Zusammenfassung aus Claude's Response extrahieren
await bot.send_message(chat_id,
    f"Vorgeschlagener Fix:\n{fix_summary}\n\nSoll ich das durchführen?",
    reply_markup=InlineKeyboardMarkup([buttons]))

Bei “Fix ausführen” wird die Claude-Session fortgesetzt mit "Freigabe erteilt. Fahre fort.". Bei “Ablehnen” wird die Session beendet.

Bonus: Kreditkarten-Workflows waren auch kaputt

Nebenbei entdeckt: Drei IMAP-basierte Workflows für Kreditkartenabrechnung waren seit der n8n 2.x Migration komplett kaputt:

ProblemFix
IMAP-Trigger feuert nicht in n8n 2.xSchedule Trigger (Cron alle 15 Min) hinzugefügt
AI Agent crasht bei leerer Mailbox (96x/Tag!)If-Guard: keine Mail → Skip
Self-Signed Cert blockiert IMAPNODE_TLS_REJECT_UNAUTHORIZED=0

Lessons Learned

  1. Monitoring nie über die überwachte Infra routen. Webhook direkt an den Service, nicht über den Reverse Proxy.

  2. n8n Node-Versionen sind ein Minenfeld. Switch v3, Webhook v2, httpRequest v4.2 — jede Version hat andere Parameter-Formate. Im Zweifel v1 nehmen und testen.

  3. $json-Kontext tracken. Nach jedem Postgres/HTTP-Node ändert sich $json. Werte die du später brauchst müssen im Output enthalten sein.

  4. Vollautomatische Remediation ist ein Anti-Pattern. KI-Diagnose + menschliche Freigabe ist der Sweet Spot. Die KI macht die Arbeit, der Mensch trägt die Verantwortung.

  5. “success” heißt nicht “funktioniert”. Ein n8n Workflow der success zeigt kann trotzdem nichts tun — wenn der If-Guard alles in den Skip-Branch schickt, ist das technisch erfolgreich.

Checkliste: Self-Healing Pipeline bauen

  • Webhook-URL intern (kein Reverse Proxy in der Kette)
  • Alert-Parser gegen echte Zabbix-Nachrichten getestet (nicht gegen die Doku)
  • Node-Versionen kompatibel mit deiner n8n-Version
  • $json-Kontext nach jedem Node verifiziert
  • Telegram-Credentials mit echten IDs (nicht Placeholder)
  • KI-Diagnose: nur vorschlagen, nicht ausführen
  • Approval-Flow: konkreter Fix im Button-Text sichtbar
  • Fallback: wenn alles fehlschlägt, trotzdem Telegram-Notification