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:
- die Indexer-Rolle steuert Indizes, Tenants und Dashboard-Funktionen;
- 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.

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:
- Syscollector inventarisiert Betriebssystem, Pakete, Ports, Prozesse und Hardware.
- Vulnerability Detection gleicht dieses Inventar mit den Wazuh-CTI-Daten ab.
- SCA bewertet die Sicherheitskonfiguration gegen passende Richtlinien.
- 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.

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 | |
| 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

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.
No comments to display
No comments to display