Einleitung
Ein hochverfügbares Wazuh-Deployment steht und fällt mit einem stabilen Indexer-Cluster. Gerade bei Multi-Node-Architekturen ist der Wazuh Indexer (OpenSearch) der kritische Pfad für Suche, Dashboards und Alarmhistorie. Ein häufiger Stolperstein entsteht, wenn Indexer-Nodes “pro Server einzeln” per Docker Compose gestartet werden: Die Container laufen zwar, aber der Cluster bildet kein Quorum – und bleibt dauerhaft bei „Wait for cluster to be available …“ hängen.
Ausgangslage / Problemstellung
Geplant ist ein Indexer-Cluster mit mehreren Nodes auf getrennten physischen Servern. Jeder Host startet “seinen” Indexer über eine eigene docker-compose.yml und eine eigene wazuhX.indexer.yml. Die Konfiguration wirkt auf den ersten Blick korrekt (eindeutige node.name, gemeinsamer cluster.name, passende discovery.seed_hosts, Zertifikate verteilt). Trotzdem zeigen die Logs:
- Node wird intern als
172.18.0.2:9300entdeckt - „… discovered … which is not a quorum“
- Wiederholt „Wait for cluster to be available …“
Symptomatisch: Die Nodes sehen nur sich selbst (oder eine “falsche” Transport-Adresse) und erreichen die Peers nicht über die vorgesehenen 192.168.110.x-Adressen.
Technische Analyse
1) Der Kernfehler: Isolierte Docker-Netzwerke pro Host
Wenn du auf jedem physischen Server eine eigene Compose-Datei startest, erzeugt Docker standardmäßig pro Host ein eigenes Bridge-Netzwerk. Dieses Netzwerk ist lokal und nicht routbar zwischen Hosts. Ergebnis:
- Container kommunizieren über interne Adressen (z. B.
172.18.0.2) - Diese Adressen existieren nur auf dem jeweiligen Host
- Der Indexer versucht die Cluster-Kommunikation (Transport/Cluster-Port 9300) über Adressen, die für andere Hosts nicht erreichbar sind
Damit können die Nodes niemals ein gemeinsames Cluster-State-Quorum bilden, egal wie sauber discovery.seed_hosts in den YAMLs aussieht.
2) Warum genau die Fehlermeldung „not a quorum“ erscheint
OpenSearch/Wazuh Indexer bildet ein Cluster über den Transport-Layer (9300). Wenn ein Node nur sich selbst (oder nicht erreichbare Peers) “entdeckt”, entsteht kein Master-Quorum. Das System bleibt dann in einer Art Warteschleife: Konfigurationen (Security-Index, Templates) warten auf einen verfügbaren Cluster.
3) Nebenaspekt, der gern zusätzlich bremst: TLS-DNs und Security-Init
Selbst wenn die Netzwerkkonnektivität stimmt, verhindern falsche Zertifikats-DNs oder nicht initialisierte Security-Indizes ebenfalls Cluster-Bildung. Entscheidend ist hier die Konsistenz zwischen:
- Zertifikat Subject/SAN (CN/SAN) je Node
plugins.security.nodes_dnin allen Node-Konfigurationen- einmalige Ausführung von
indexer-security-init.shnach Zertifikats-/Config-Änderungen
Netzwerk ist in diesem Fall aber der „Showstopper“: Ohne 9300-Konnektivität ist jede weitere Fehlersuche zweitrangig.
Lösung / Best Practices
Lösung A: Host-Netzwerkmodus nutzen (typischster Fix für “Container pro Host”)
Wenn jeder Indexer-Container auf einem eigenen physischen Server läuft und du keine Overlay-Netzwerke einsetzen willst, ist der pragmatischste Ansatz:
In jedem Indexer-Service in der Compose-Datei:
network_mode: "host"
Wirkung:
- Der Container nutzt das Host-Netzwerk direkt
- Ports werden nicht mehr per Docker-NAT/Bridge abstrahiert
network.hostunddiscovery.seed_hostskönnen sauber auf die realen Host-IPs bzw. Hostnames zeigen- Die Transport-Kommunikation über 9300 funktioniert host-zu-host
Wichtig dabei:
- Bei
network_mode: "host"entfallen typischeports:-Mappings (sie sind wirkungslos bzw. redundant), weil Dienste direkt auf Host-Ports binden. - Stelle sicher, dass 9200/TCP (HTTP) und 9300/TCP (Transport) zwischen den Hosts offen sind (Firewall/Security Groups).
Lösung B: Gemeinsames, routbares Netzwerk für Container (Overlay/Swarm/Kubernetes)
Wenn du zwingend bei Bridge-Netzen bleiben willst, brauchst du ein Netzwerk, das container-to-container über Hosts routet (Overlay). Das ist operational aufwendiger, aber technisch sauber. In klassischen Docker-Compose-on-bare-metal Szenarien ist Host Networking meist die stabilere Abkürzung.
Konfiguration: Cluster-Parameter konsistent halten, aber korrekt “einschwingen”
Für die Indexer-Konfiguration gilt:
cluster.name: identisch auf allen Nodesnode.name: eindeutig je Nodediscovery.seed_hosts: Liste aller master-eligible Nodes (IP oder resolvable Hostnames)cluster.initial_master_nodes: nur für den initialen Cluster-Bootstrap relevant- Best Practice: Nach erfolgreicher Clusterbildung bei späteren Restarts nicht als dauerhafte “Fehlerquelle” missbrauchen, sondern bewusst nur für die erste Initialisierung verwenden (und dabei sicherstellen, dass Node-Namen exakt übereinstimmen).
Zertifikate und Security: DN-Matching und Initialisierung sauber durchführen
plugins.security.nodes_dnmuss die DN-Werte der Node-Zertifikate enthalten (CN/SAN muss zur Node-Identität passen).- Nach Änderungen an Zertifikaten/Config: Security-Init einmal ausführen (auf einem Node reicht):
/usr/share/wazuh-indexer/bin/indexer-security-init.sh
Minimaler Validierungs-Check vor “Wazuh ist kaputt”
- Von Indexer1 zu Indexer2:
- TCP 9300 erreichbar? (Transport)
- TCP 9200 erreichbar? (HTTP, optional zur Diagnose)
- In den Indexer-Logs prüfen, ob Peers mit 192.168.110.x:9300 auftauchen (nicht 172.18.x.x).
- Cluster-Health (wenn HTTP erreichbar) prüfen und erst dann Filebeat/Wazuh-Manager/Dashboard weiter integrieren.
Lessons Learned / Best Practices
- Indexer-Cluster ist Transport-getrieben: Port 9300 ist für Clusterbildung zwingend, 9200 ist nur HTTP/API.
- “Compose pro Host” erzeugt per Default Isolation: Ohne Host Networking oder Overlay-Netzwerk können Nodes auf unterschiedlichen Hosts nicht clustern.
- Netzwerk zuerst, Security danach: Quorum-/Discovery-Probleme sind fast immer Routing/Ports/Interfaces, nicht Rule- oder Zertifikatsdetails.
- Zertifikate strikt konsistent halten: CN/SAN ↔
nodes_dnmüssen passen, sonst blockiert Security die Cluster-Kommunikation zusätzlich. - Bootstrap bewusst managen:
cluster.initial_master_nodesist ein Initialisierungsmechanismus, kein dauerhafter “Cluster-Wert”, den man beliebig überall lässt.
Fazit
Der Quorum-Fehler im Wazuh Indexer bei verteilten Docker-Deployments entsteht in der Praxis meist nicht durch YAML-Syntax, sondern durch Netzwerk-Topologie: Separate Docker-Bridge-Netze pro Host verhindern, dass sich Indexer-Nodes über ihre realen IPs auf Port 9300 erreichen. Mit network_mode: "host" (oder einem echten Overlay-Netzwerk) wird die Clusterkommunikation stabil, sodass Discovery, Masterwahl und Security-Initialisierung sauber greifen – und der Indexer-Cluster zuverlässig hochkommt.
https://wazuh.slack.com/archives/C07BZJY86G3/p1765449682911369