Skip to main content

02.2 Wazuh Grundkonfiguration – technische Umsetzung

Einleitung

Die Installation steht, das Dashboard geht auf und die ersten Dienste sind grün. Jetzt kommt der Teil, der aus einer frischen Wazuh-Installation eine brauchbare Sicherheitsplattform macht.

Diese Seite ist die technische Ergänzung zu 02.1 Wazuh Grundkonfiguration. Dort geht es um Reihenfolge und Entscheidungen. Hier stehen die konkreten Schritte, Kommandos und Prüfungen, die ich bei meiner All-in-one-Installation wirklich verwendet habe.

Absichtlich noch nicht enthalten sind Docker, Nextcloud, Home Assistant, Vaultwarden und Reverse-Proxy-Logs. Das sind keine Eigenschaften der Wazuh-Basis, sondern eigene Datenquellen mit eigenen Filtern und Alarmen. Sie bekommen später im Kapitel 03 jeweils eine eigene Seite.

Ausgangspunkt

Die Anleitung beginnt nach der abgeschlossenen Installation:

  • Wazuh Server, Indexer und Dashboard laufen auf einem gemeinsamen Host;
  • das Betriebssystem ist Rocky Linux 10.2;
  • die Wazuh-Komponenten haben den Stand 4.14.7;
  • SELinux läuft im Modus Enforcing;
  • Managerdaten und Indexerdaten besitzen getrennte Dateisysteme;
  • Dashboard, API und Indexer wurden technisch geprüft;
  • ein funktionierender SSH-Zugang mit persönlichem Schlüssel ist vorhanden.

Interne Namen, Adressen und Kennwörter sind in den Beispielen durch neutrale Werte ersetzt:

Wazuh-Host:       wazuh01.example.internal
Dashboard:        https://wazuh.example.internal
Wazuh-API:        https://127.0.0.1:55000
Indexer-API:      https://127.0.0.1:9200
Agentenverkehr:   TCP 1514
Enrollment:       TCP 1515
Dashboard-Backend: TCP 5601

Schritt 1: Erst eine belastbare Ausgangslage festhalten

Bevor ich Konten, Agents oder Regeln angefasst habe, habe ich Versionen, Dienste, Listener und Dateisysteme aufgenommen:

cat /etc/rocky-release
uname -r
getenforce

rpm -q wazuh-manager wazuh-indexer wazuh-dashboard filebeat

systemctl is-active \
  wazuh-manager wazuh-indexer wazuh-dashboard filebeat
systemctl is-enabled \
  wazuh-manager wazuh-indexer wazuh-dashboard filebeat

ss -lnt
df -hT / /var/ossec /var/lib/wazuh-indexer
df -ih / /var/ossec /var/lib/wazuh-indexer

Beim letzten Abgleich waren alle vier zentralen Dienste aktiv. Die Wazuh-API lauschte nur auf 127.0.0.1:55000, der Indexer nur auf 127.0.0.1:9200. Agentenkommunikation und Enrollment waren auf TCP 1514 und 1515 erreichbar. Genau diese Trennung ist beabsichtigt: Agents brauchen weder die Manager-API noch direkten Zugriff auf den Indexer.

Die Managerkonfiguration lässt sich zusätzlich über die API validieren:

TOKEN=$(curl --silent --user '<API-USER>:<API-PASSWORT>' \
  --request POST \
  'https://127.0.0.1:55000/security/user/authenticate?raw=true')

curl --silent \
  --header "Authorization: Bearer ${TOKEN}" \
  'https://127.0.0.1:55000/manager/configuration/validation'

In meiner Installation antwortet der Manager mit status: OK und ohne fehlgeschlagene Elemente. Das Token gehört trotzdem nicht in die Shell-Historie einer echten Umgebung. Für wiederholbare Prüfungen verwende ich eine geschützte Env-Datei oder einen Secret-Store.

Schritt 2: Erreichbarkeit auf die echten Datenflüsse begrenzen

Für die Grundkonfiguration habe ich die Flows einzeln betrachtet:

Quelle Ziel Port Zweck
überwachte Systeme Wazuh Server 1514/TCP laufende Agentenkommunikation
vorgesehene Enrollment-Netze Wazuh Server 1515/TCP erstmalige Anmeldung
Dashboard Wazuh Server 55000/TCP lokale Manager-API
Wazuh Server / Filebeat Indexer 9200/TCP lokale, TLS-gesicherte Übergabe
administratives Netz oder Proxy Dashboard 443/TCP beziehungsweise 5601/TCP Weboberfläche

API und Indexer bleiben bei meinem All-in-one-Aufbau auf Loopback. Der Dashboard-Backendport ist nur aus der Richtung erreichbar, aus der die Webanfragen tatsächlich kommen. Nicht benötigte UDP-Varianten, Syslog 514 und Clusterports bleiben geschlossen.

Nach jeder Firewalländerung prüfe ich nicht nur die Regeln, sondern auch den echten Verbindungsweg:

sudo firewall-cmd --list-all
sudo firewall-cmd --list-rich-rules
ss -lnt

curl --fail --silent --show-error \
  https://wazuh.example.internal/ >/dev/null

Ein offener Listener und eine Firewalld-Regel sind zwei verschiedene Dinge. Beides muss zum Zielbild passen.

Schritt 3: TLS und Zeit nicht als Nebensache behandeln

Wazuh verbindet mehrere Komponenten, die Zertifikate und Zeitstempel verwenden. Deshalb habe ich für jede TLS-Strecke geprüft:

  • stimmt der aufgerufene Name mit einem SAN im Zertifikat überein?
  • ist die komplette Zertifikatskette vertrauenswürdig?
  • kann nur der jeweilige Dienst den privaten Schlüssel lesen?
  • ist das Ablaufdatum überwacht?
  • bleibt auch die interne Verbindung zum Indexer TLS-geschützt?

Für ein Zertifikat am Dashboard sieht die Prüfung beispielsweise so aus:

openssl s_client \
  -connect wazuh.example.internal:443 \
  -servername wazuh.example.internal \
  -verify_return_error </dev/null

openssl s_client \
  -connect wazuh.example.internal:443 \
  -servername wazuh.example.internal </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

Die Uhr prüfe ich getrennt vom bloßen Dienststatus:

systemctl is-active chronyd
chronyc tracking
chronyc sources -n
chronyc authdata

Ein laufendes chronyd beweist noch nicht, dass eine brauchbare und – wenn gewünscht – per NTS authentifizierte Quelle verwendet wird.

Schritt 4: Bootstrap-Konten aus dem Alltag nehmen

Die Installation erzeugt administrative Konten, die für Bootstrap und Notfälle praktisch sind. Für den täglichen Betrieb habe ich sie nicht weiterverwendet. Mein Kontenmodell besteht aus:

Kontotyp Zweck Rechte
eingebautes Administratorkonto Break-glass und Wiederherstellung vollständig
persönliches Administratorkonto geplante Änderungen administrativ
persönliches Read-only-Konto tägliche Sichtung lesend
technisches Reporting-Konto spätere Berichte lesend und minimal

Wazuh besitzt dabei zwei Berechtigungsebenen, die zusammenpassen müssen:

  1. die Indexer-Rolle steuert Indizes, Tenants und Dashboard-Funktionen;
  2. die Wazuh-Serverrolle steuert API-Aktionen und sichtbare Agentdaten.

Nur eine der beiden Seiten zu konfigurieren führt gern zu einem Konto, das sich anmelden kann, aber entweder zu viel oder überraschend wenig sieht.

In meinem Repository übernimmt ein idempotent gedachtes Provisionierungsskript diese Arbeit. Es erhält die Geheimnisse ausschließlich aus einer nicht versionierten JSON-Datei:

python3 provision-users.py \
  --users-file /run/secrets/wazuh-users.json \
  --api-url https://127.0.0.1:55000 \
  --indexer-url https://127.0.0.1:9200

Die Eingabedatei enthält getrennte Konten und zufällig erzeugte Kennwörter. Das Skript legt eine eigene Read-only-Indexerrolle an, ordnet die Wazuh-Serverrolle zu und prüft anschließend die effektiven Rechte. Vorhandene Zielkonten werden nicht still überschrieben.

Für die Abnahme melde ich mich anschließend wirklich mit beiden persönlichen Konten am Dashboard an:

  • das Administratorkonto darf Konfigurationen ändern;
  • das Read-only-Konto kann Alerts und Bestände lesen;
  • das Read-only-Konto kann weder Benutzer noch Regeln oder Agents verändern;
  • das eingebaute Konto wird wieder abgemeldet und sicher verwahrt.

Schritt 5: Ein bewusstes Enrollment-Verfahren wählen

In meiner kleinen, vollständig kontrollierten Umgebung verwendet der Enrollment-Dienst ein gemeinsames starkes Kennwort. Das ist keine universelle Empfehlung für fremde Clients oder offene Netze, für ein überschaubares Homelab aber ein vertretbarer Start.

Das Kennwort liegt außerhalb von Git und Wiki. Die Managerkonfiguration wird vor der Änderung gesichert und anschließend geprüft:

<auth>
  <disabled>no</disabled>
  <remote_enrollment>yes</remote_enrollment>
  <port>1515</port>
  <use_password>yes</use_password>
  <ciphers>HIGH:!ADH:!EXP:!MD5:!RC4:!3DES:!CAMELLIA:@STRENGTH</ciphers>
  <ssl_verify_host>no</ssl_verify_host>
</auth>

Damit bleibt der Enrollment-Dienst aktiv und lauscht für neue Agents mit gemeinsamem Kennwort auf TCP 1515; die OpenSSL-Cipherliste lässt zunächst nur als HIGH eingestufte Verfahren zu, entfernt mit jedem ! dauerhaft anonymes Diffie-Hellman ohne Gegenstellen-Authentisierung (ADH, nicht das normale authentisierte DHE), alte Exportverfahren (EXP), MD5-, RC4-, 3DES- und CAMELLIA-Suites und sortiert den verbleibenden Rest mit @STRENGTH nach Schlüsselstärke, während ssl_verify_host=no die zusätzliche Prüfung der Agentenquell-IP gegen den Zertifikats-CN deaktiviert, die ohnehin erst zusammen mit einer konfigurierten Agenten-CA greift.

Der Passwortwert selbst steht nicht in diesem XML-Block. Er wird als geschützte Datei auf dem Manager bereitgestellt. Danach folgen Syntaxprüfung, Managerneustart und Listenerkontrolle:

sudo /var/ossec/bin/wazuh-authd -t
sudo systemctl restart wazuh-manager
sudo systemctl is-active wazuh-manager
ss -lnt | grep ':1515 '

Port 1515 ist nur aus den Netzen erreichbar, aus denen ich tatsächlich neue Agents anmelde. Bereits registrierte Agents benötigen für den normalen Betrieb nur Port 1514.

Bei unbekannten Clients, Gastnetzen, externem Enrollment oder mehreren Administratoren würde ich zusätzlich Zertifikatsprüfung und eine strengere Netztrennung einsetzen.

Schritt 6: Den ersten Agent klein und kontrolliert aufnehmen

Ich beginne nicht mit allen Systemen gleichzeitig. Der erste Agent dient dazu, Enrollment, Kommunikation, Inventar und Konfigurationsabgleich Ende zu Ende zu prüfen.

Die Installationskommandos lasse ich mir im Dashboard unter Deploy new agent für das jeweilige Betriebssystem erzeugen. Sinngemäß werden Manager, Agentenname und Enrollment-Passwort gesetzt; das Geheimnis steht dabei nicht in der Dokumentation:

sudo WAZUH_MANAGER='wazuh01.example.internal' \
  WAZUH_AGENT_NAME='server01.example.internal' \
  WAZUH_REGISTRATION_PASSWORD='<AUS-SECRET-STORE>' \
  rpm -ivh wazuh-agent-<VERSION>.rpm

sudo systemctl enable --now wazuh-agent

Danach prüfe ich auf dem Agent:

systemctl is-active wazuh-agent
systemctl is-enabled wazuh-agent
sudo tail -n 80 /var/ossec/logs/ossec.log

Und auf der Managerseite:

sudo /var/ossec/bin/agent_control -l
sudo /var/ossec/bin/agent_groups -l -g default

Im Dashboard müssen Status, Betriebssystem und Version plausibel erscheinen. Ein alter oder doppelter Agent wird kontrolliert entfernt; force_reenroll ist kein Standardmittel gegen unklare Bestände.

Anonymisierte Endpoint-Übersicht mit Status und Betriebssystemen

Der reale Endpoint-Bestand, aber ohne Hostnamen, IP-Adressen, Gruppenprofile oder Agententabelle. Für die Grundkonfiguration reichen Status und Betriebssystemmix völlig aus.

Schritt 7: Zentrale Agentengruppen vorbereiten

Agentengruppen halten wiederkehrende Einstellungen vom einzelnen Endpoint fern. Ich verwende eine additive Struktur:

default
├── gemeinsame, wirklich sichere Basis
├── os-linux oder os-windows
└── spätere Rollenprofile

Die default-Gruppe bleibt bewusst klein. Ein einfacher Ausgangspunkt sieht so aus:

<agent_config>
  <labels>
    <label key="environment">production</label>
  </labels>

  <syscheck>
    <disabled>no</disabled>
    <frequency>43200</frequency>
    <scan_on_start>yes</scan_on_start>
  </syscheck>

  <wodle name="syscollector">
    <disabled>no</disabled>
    <interval>1h</interval>
    <scan_on_start>yes</scan_on_start>
    <hardware>yes</hardware>
    <os>yes</os>
    <network>yes</network>
    <packages>yes</packages>
    <ports all="no">yes</ports>
    <processes>yes</processes>
  </wodle>
</agent_config>

Vor dem Rollout wird jede Fassung validiert:

sudo install -o wazuh -g wazuh -m 0660 \
  /tmp/agent.conf \
  /var/ossec/etc/shared/default/agent.conf.new

sudo /var/ossec/bin/verify-agent-conf \
  -f /var/ossec/etc/shared/default/agent.conf.new

Erst danach ersetze ich die produktive Datei. Nach der nächsten Agentensynchronisation muss die API für alle betroffenen Agents group_config_status: synced melden.

Die konkreten Profile für Anwendungen und Plattformen gehören nicht auf diese Basisseite. Sie folgen in Kapitel 03.

Schritt 8: Die grundlegenden Module in sinnvoller Reihenfolge prüfen

Vier Funktionen liefern für fast jedes normale System einen guten ersten Nutzen:

  1. Syscollector inventarisiert Betriebssystem, Pakete, Ports, Prozesse und Hardware.
  2. Vulnerability Detection gleicht dieses Inventar mit den Wazuh-CTI-Daten ab.
  3. SCA bewertet die Sicherheitskonfiguration gegen passende Richtlinien.
  4. FIM/Syscheck überwacht gezielt wichtige Konfigurationspfade.

Ich schalte nicht sofort ganze Dateisysteme oder alle Logquellen frei. Erst prüfe ich pro Modul, ob Daten ankommen und welches Volumen entsteht.

Wazuh-Übersicht mit Endpoint Security und Threat Intelligence

Die Funktionsbereiche sind sofort sichtbar. Wirklich nützlich werden sie aber erst, wenn der Agentbestand sauber ist und die Datenquellen bewusst gewählt wurden.

Für den ersten Agent kontrolliere ich im Dashboard:

  • unter IT Hygiene, ob Betriebssystem, Pakete und Prozesse erscheinen;
  • unter Vulnerability Detection, ob ein vollständiger Bestand aufgebaut wurde;
  • unter Configuration Assessment, ob eine passende Policy gelaufen ist;
  • unter File Integrity Monitoring, ob nur erwartete Pfade gemeldet werden.

Ein fehlender Vulnerability-Bestand ist oft kein Indexerproblem, sondern ein unvollständiger oder noch nicht abgeschlossener Syscollector-Lauf.

Schritt 9: Log- und Kapazitätsgrenzen einbauen

Ein Security-System darf nicht ausgerechnet an seinen eigenen Logs sterben. Auf dem zentralen Host habe ich deshalb das persistente Journal begrenzt:

[Journal]
Storage=persistent
SystemMaxUse=1G
SystemKeepFree=2G
SystemMaxFileSize=128M
MaxRetentionSec=30day
RuntimeMaxUse=256M
RateLimitIntervalSec=30s
RateLimitBurst=10000
Compress=yes
Seal=yes

Nach der Aktivierung prüfe ich:

sudo systemctl restart systemd-journald
journalctl --disk-usage
sudo journalctl --verify

Zusätzlich läuft bei mir alle fünf Minuten ein kleiner Kapazitätsmonitor. Er prüft Blöcke und Inodes von /, /var/ossec und /var/lib/wazuh-indexer. Er meldet nur Zustandswechsel und Recovery statt alle fünf Minuten denselben Wert zu erzeugen.

Die verwendeten Startschwellen sind:

Zustand Blöcke oder Inodes einzelne Logdatei
Warnung ab 70 % ab 1 GiB
Kritisch ab 80 % ab 5 GiB
Notfall ab 90 % ab 10 GiB

Das schützt das Dateisystem, ersetzt aber keine Index-Retention.

Schritt 10: Alarmierung erst nach einer brauchbaren Baseline einschalten

Ein einzelner Loginfehler oder ein normaler Zustandswechsel gehört zunächst ins Dashboard. Externe Nachrichten sind für bestätigte oder wirklich wichtige Zustände reserviert.

Mein Grundrouting beginnt bei diesen Schwellen:

Wazuh-Level Behandlung
0 bis 11 Dashboard und spätere Berichte
12 bis 14 Mail
ab 15 Mail und zusätzlicher Sofortkanal

Die Manager-Baseline bleibt dabei nachvollziehbar:

<alerts>
  <log_alert_level>3</log_alert_level>
  <email_alert_level>12</email_alert_level>
</alerts>

Für den Versand verwende ich eine eigene Integration. Sie erhält Secrets aus einer geschützten Datei und überträgt nur Level, Regel-ID, Beschreibung, System, Gruppen, Zeit und Dashboard-Link. Rohe Logzeilen, Tokens und unnötige personenbezogene Daten werden nicht an externe Kanäle geschickt.

Vor dem Neustart prüfe ich die Konfiguration:

sudo /var/ossec/bin/wazuh-analysisd -t
sudo systemctl restart wazuh-manager
sudo systemctl is-active wazuh-manager

Eigene Regeln teste ich zuerst mit wazuh-logtest. Danach folgt genau ein kontrollierter Ende-zu-Ende-Test. Eine Testmail beweist den Mailweg; sie beweist noch nicht, dass die gewünschte Regel unter echten Bedingungen auslöst.

Schritt 11: Retention bewusst festlegen

Der Indexer löscht alte Alert-Indizes nicht allein deshalb passend, weil die Disk irgendwann knapp wird. Dafür braucht er eine Index State Management Policy.

In meiner Umgebung ist der unabhängige Dateisystemschutz bereits aktiv. Die eigentliche ISM-Retention ist beim Stand dieser Seite noch ein offener Betriebsschritt. Das schreibe ich lieber ehrlich hin, statt eine nur geplante Policy als erledigt zu verkaufen.

Mein Startwert für die kleine Umgebung sind 90 Tage durchsuchbare Alerts. Die Policy bekommt:

Policy ID:       wazuh-alert-retention-policy
Index pattern:   wazuh-alerts-*
Initial state:   retention_state
Transition:      nach 90 Tagen
Final action:    delete

Im Dashboard führt der Weg über Indexer management → Index Management → State management policies. Die Policy muss sowohl ein ISM-Template für neue Indizes besitzen als auch kontrolliert auf bereits vorhandene Alert-Indizes angewendet werden.

Danach prüfe ich in den Dev Tools:

GET _plugins/_ism/policies/wazuh-alert-retention-policy
GET _plugins/_ism/explain/wazuh-alerts-*
GET _cat/indices/wazuh-alerts-*?v&s=index
GET _cat/allocation?v&s=node

Grüner Indexer-Clusterzustand in den Dev Tools

Ein grüner Cluster ist die Voraussetzung für einen ruhigen Betrieb. Er sagt aber noch nichts darüber aus, ob die Retention-Policy wirklich an den Alert-Indizes hängt.

Schritt 12: Backup und Updateweg festlegen

Für die Grundkonfiguration sichere ich mindestens:

  • /var/ossec/etc/ mit Managerkonfiguration, Gruppen, Regeln und Decodern;
  • Dashboard- und Indexer-Konfiguration;
  • Zertifikate und den dokumentierten Wiederbeschaffungsweg;
  • RBAC- und Benutzerdefinitionen ohne Klartextkennwörter;
  • die extern verschlüsselte Secret-Ablage;
  • Indexer-Snapshots, falls die Alert-Historie wiederherstellbar sein muss.

Ein VM-Snapshot ist ein praktischer Rückweg vor einer Änderung, aber kein vollständiges Backup eines laufenden Indexers. Für meine kleine Umgebung kann ein dokumentierter Neuaufbau plus Konfigurationsrestore sinnvoller sein als die dauerhafte Sicherung jeder historischen Meldung. Entscheidend ist, dass der gewählte Weg einmal wirklich getestet wird.

Die zentralen Wazuh-Pakete aktualisiere ich gemeinsam und kontrolliert. Vorher prüfe ich Release Notes, freien Platz, Snapshot beziehungsweise Backup und den Rückweg. Danach werden Versionen, Dienste, API, Indexer, Dashboard und ein echter Agent erneut abgenommen.

Schritt 13: Die Grundkonfiguration abnehmen

Meine kurze technische Abnahme lautet:

systemctl is-active \
  wazuh-manager wazuh-indexer wazuh-dashboard filebeat

sudo /var/ossec/bin/wazuh-analysisd -t
sudo /var/ossec/bin/verify-agent-conf

df -hT / /var/ossec /var/lib/wazuh-indexer
df -ih / /var/ossec /var/lib/wazuh-indexer

journalctl --disk-usage
systemctl list-timers \
  bp7-capacity-monitor.timer \
  bp7-posture-monitor.timer

Zusätzlich kontrolliere ich über API und Dashboard:

  • Managerkonfiguration ist OK;
  • keine produktiv benötigte Managerkomponente ist ausgefallen;
  • Agents sind aktiv oder ihr abweichender Zustand ist erklärt;
  • Gruppenkonfigurationen stehen auf synced;
  • Syscollector, SCA, Vulnerability Detection und FIM liefern plausible Daten;
  • ein kontrollierter Alarm erscheint im Index und im erwarteten Kanal;
  • Indexerzustand, freie Kapazität und ISM-Zuordnung sind bekannt;
  • Admin- und Read-only-Konto verhalten sich wie geplant.

Beim letzten Abgleich waren zwölf überwachte Agents registriert, elf aktiv und einer getrennt. Alle zwölf Gruppenkonfigurationen waren synchronisiert. Die Managerkonfiguration meldete OK, und alle zentralen Dienste sowie beide Kapazitätstimer waren aktiv. Der getrennte Agent bleibt damit ein sichtbarer Betriebsbefund und wird nicht durch eine hübsche Gesamtampel versteckt.

Was erst in Kapitel 03 kommt

Die Basis ist jetzt fertig beschrieben. Die nächsten Seiten dürfen sich gezielt um einzelne Datenquellen kümmern:

  • Docker-Ereignisse und Container-FIM;
  • Nextcloud-AIO-Logs und Loginfehler;
  • Home-Assistant-Journald und Queue-Tuning;
  • Reverse-Proxy-Zugriffslogs;
  • Vaultwarden;
  • anwendungsspezifische Regeln, Filter und Alarmkorrelationen.

Dadurch bleibt diese Seite auch dann verständlich, wenn keine einzige dieser Anwendungen in einer anderen Umgebung existiert.

Weiterlesen

Zuletzt aktualisiert am 18.08.2026 · 2 Aufrufe