Ein Windows-Server startet neu. Geplant, kontrolliert, kein Drama.

Dein Handy sieht das anders. 65 Benachrichtigungen. Jeder einzelne Windows-Dienst der beim Hochfahren kurz nicht lief, bekommt seinen eigenen Alert. Zwei Minuten später: 65 Recovery-Meldungen. Du scrollst durch 130 Nachrichten um festzustellen — alles in Ordnung.

Das ist kein Monitoring. Das ist Spam.


Das Problem

Zabbix entdeckt Windows-Dienste per Low-Level Discovery (LLD). Für jeden Dienst mit Startup-Typ “automatic” wird ein Trigger erstellt:

"PrintSpooler" (Druckwarteschlange) is not running (startup type automatic)

Bei einem Reboot passiert folgendes:

  1. Server fährt runter → Agent wird unerreichbar
  2. Server bootet → Agent startet → Dienste starten nacheinander
  3. Zabbix pollt und sieht: 65 Dienste nicht gestartet → 65 Alerts
  4. 30 Sekunden später: Dienste laufen → 65 Recovery-Meldungen

Zwischen Schritt 2 und 4 liegt die Flut.

Die Lösung: Trigger Dependencies

Zabbix hat ein Feature genau dafür: Trigger Dependencies. Die Logik:

Wenn Trigger A (Parent) im PROBLEM-Status ist, werden alle abhängigen Trigger B, C, D unterdrückt.

Der Plan:

  • Parent-Trigger: “Host has been restarted (uptime < 30m)”
  • Abhängige Trigger: Alle “Service is not running” Trigger

Bei einem Reboot feuert nur der Uptime-Trigger. Alle Service-Alerts bleiben still. Nach 30 Minuten löst sich der Parent — und wenn dann ein Dienst immer noch nicht läuft, feuert dessen Trigger ganz normal.

Eine Meldung statt 65.

Die Umsetzung

Schritt 1: Den richtigen Hebel finden

Die Service-Trigger werden per LLD erstellt — du kannst sie nicht einzeln anfassen. Aber du kannst den Trigger-Prototypen auf dem Template ändern. Das ist der Bauplan, aus dem LLD die konkreten Trigger generiert.

Zuerst die Discovery Rule auf dem Template finden:

# Template: "Windows by Zabbix agent"
curl -sk -X POST "https://<DEIN-ZABBIX>/api_jsonrpc.php" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "discoveryrule.get",
    "params": {
      "hostids": ["<TEMPLATE-ID>"],
      "search": {"name": "service"},
      "output": ["itemid", "name"]
    },
    "auth": "<TOKEN>",
    "id": 1
  }'

Dann den Trigger-Prototypen:

curl -sk -X POST "https://<DEIN-ZABBIX>/api_jsonrpc.php" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "triggerprototype.get",
    "params": {
      "discoveryids": ["<DISCOVERY-RULE-ID>"],
      "output": ["triggerid", "description"],
      "selectDependencies": ["triggerid"]
    },
    "auth": "<TOKEN>",
    "id": 1
  }'

Du brauchst zwei IDs:

  • Die Trigger-Prototype-ID des Service-Triggers
  • Die Trigger-ID des Uptime-Triggers auf demselben Template

Schritt 2: Dependency auf dem Prototypen setzen

curl -sk -X POST "https://<DEIN-ZABBIX>/api_jsonrpc.php" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "triggerprototype.update",
    "params": {
      "triggerid": "<SERVICE-PROTOTYPE-ID>",
      "dependencies": [
        {"triggerid": "<UPTIME-TRIGGER-ID>"}
      ]
    },
    "auth": "<TOKEN>",
    "id": 1
  }'

Wenn du sowohl passive als auch active Agent-Templates nutzt, musst du das auf beiden Templates machen.

Schritt 3: Der Pitfall — bestehende Trigger

Jetzt der Teil den die Doku verschweigt.

Die Prototype-Dependency gilt nur für neue Trigger die LLD in Zukunft erstellt. Die hunderte bestehenden Service-Trigger auf deinen Hosts? Unverändert. Kein Dependency. Kein Schutz.

Dein erster Instinkt: trigger.update auf die bestehenden Trigger.

"Cannot update dependencies for a discovered trigger"

Zabbix erlaubt kein trigger.update mit dependencies auf discovered Triggers. Punkt. Kein Workaround.

Die Lösung: LLD muss die Trigger neu verarbeiten. Beim nächsten Discovery-Lauf liest LLD den aktualisierten Prototypen und propagiert die Dependencies auf alle bestehenden Trigger.

Das Problem: Die Discovery Rule läuft nur stündlich (Default). Wenn du 50 Hosts hast, wartest du bis zu einer Stunde.

Schritt 4: LLD-Rerun forcieren

Per API kannst du einen sofortigen Discovery-Lauf erzwingen:

# Discovery Rule ID des Hosts ermitteln
curl -sk -X POST "https://<DEIN-ZABBIX>/api_jsonrpc.php" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "discoveryrule.get",
    "params": {
      "hostids": ["<HOST-ID>"],
      "search": {"key_": "service.discovery"},
      "output": ["itemid"]
    },
    "auth": "<TOKEN>",
    "id": 1
  }'

# Sofortigen Check auslösen
curl -sk -X POST "https://<DEIN-ZABBIX>/api_jsonrpc.php" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "task.create",
    "params": {
      "type": 6,
      "request": {
        "itemid": "<DISCOVERY-RULE-ID>"
      }
    },
    "auth": "<TOKEN>",
    "id": 1
  }'

Das machst du einmal pro Host. Bei 50 Hosts ein kurzes Bash-Script, nach 20 Sekunden sind die Dependencies überall angekommen.

Achtung: Hosts die aktuell nicht überwacht werden (disabled, nicht erreichbar) bekommen die Dependencies automatisch beim nächsten erfolgreichen LLD-Lauf — sobald sie wieder online sind.

Schritt 5: Uptime-Threshold anpassen

Der Standard-Trigger “Host has been restarted” prüft uptime < 10m. Das kann zu knapp sein:

  • Server mit vielen Diensten brauchen länger zum Hochfahren
  • Hohe CPU-Last beim Booten verzögert den Zabbix-Agent-Start
  • Wenn der Agent erst nach 10 Minuten antwortet, verpasst Zabbix den niedrigen Uptime-Wert komplett

Sicherer: 30 Minuten.

curl -sk -X POST "https://<DEIN-ZABBIX>/api_jsonrpc.php" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "trigger.update",
    "params": {
      "triggerid": "<TEMPLATE-UPTIME-TRIGGER-ID>",
      "expression": "last(/<TEMPLATE-NAME>/system.uptime)<30m"
    },
    "auth": "<TOKEN>",
    "id": 1
  }'

Template-Änderungen propagieren automatisch auf alle Hosts — hier kein LLD-Rerun nötig.

Das Ergebnis

Vorher:

14:23:01  PROBLEM  SRV-R2D2: Host has been restarted (uptime < 10m)
14:23:03  PROBLEM  SRV-R2D2: "Spooler" is not running
14:23:03  PROBLEM  SRV-R2D2: "W32Time" is not running
14:23:04  PROBLEM  SRV-R2D2: "WinRM" is not running
... (62 weitere)
14:25:31  OK       SRV-R2D2: "Spooler" recovered
14:25:31  OK       SRV-R2D2: "W32Time" recovered
... (62 weitere)

130 Nachrichten. Null Informationsgehalt.

Nachher:

14:23:01  PROBLEM  SRV-R2D2: Host has been restarted (uptime < 30m)
14:52:32  OK       SRV-R2D2: Host has been restarted - recovered

Zwei Nachrichten. Volle Information.

Lessons Learned

  1. Trigger Dependencies gehören in den Prototypen — nicht auf die einzelnen Trigger. Die Zabbix API verhindert letzteres bei discovered Triggers aktiv.

  2. Prototype-Änderungen brauchen einen LLD-Rerun um auf bestehende Trigger zu wirken. task.create mit Type 6 forciert das sofort.

  3. 10 Minuten Uptime-Threshold ist zu knapp für moderne Windows-Server. 30 Minuten gibt Puffer für langsame Boots, hohe CPU-Last, und verzögerten Agent-Start.

  4. Die beste Alert-Regel ist die, die nicht feuert wenn sie nicht soll. Alert Fatigue ist real — wer 65 Alerts pro Reboot bekommt, ignoriert irgendwann auch den einen der zählt.

Checkliste

  • Trigger-Prototypen identifiziert (Template → Discovery Rule → Prototype)
  • Dependency gesetzt: Service-Prototype → Uptime-Trigger (pro Template)
  • LLD-Rerun auf allen aktiven Hosts forciert (task.create type 6)
  • Uptime-Threshold geprüft und ggf. angepasst (Empfehlung: 30m)
  • Trigger-Description aktualisiert (falls Threshold geändert)
  • Testlauf: Server neustarten und prüfen ob nur Uptime-Alert kommt