Introduction
Wazuh is an open-source security platform that unifies host-based intrusion detection (HIDS), log analysis, file integrity monitoring (FIM), vulnerability detection, and SIEM-style correlation and alerting into a single agent/manager architecture. It began as a fork of OSSEC and has since grown into a full stack: lightweight agents on every monitored host, a manager that decodes and correlates events against rules, an OpenSearch-based indexer for storage and search, and a dashboard for visualizing alerts.
This guide treats Wazuh the way a production security team actually runs it: not just “install the agent and look at a dashboard,” but understanding the decoder → rule → alert pipeline well enough to write your own detections, tune out noise, and trace a real attack (SSH brute forcing) end-to-end from raw log line to a dashboard alert.
Who this is for
- DevOps/SysAdmin engineers adding detection capability to a fleet of servers.
- Anyone who has run a SIEM concept in theory but never built the manager, indexer, and agent pipeline by hand.
- Security-conscious teams who want alerting that maps to MITRE ATT&CK, not just raw logs.
Prerequisites
You should have the following before deploying the stack in this guide:
- A Linux host (Ubuntu 22.04/RHEL 9 recommended) with at least 4 vCPU / 8 GB RAM for the manager + indexer + dashboard (all-in-one), or separate hosts for a distributed install.
- Root or sudo access on the manager host and every host you intend to monitor.
- Basic familiarity with systemd, YAML, and reading XML configuration files.
- Outbound access from monitored hosts to the manager on TCP 1514 (event data) and 1515 (agent enrollment).
- Docker and Docker Compose installed if you plan to follow the containerized install path.
Sizing matters
The Wazuh indexer is an OpenSearch cluster under the hood — it is the most resource-hungry component. Undersized indexer nodes are the number one cause of a “working” Wazuh install that silently stops ingesting alerts under load.Theory
What a SIEM/HIDS actually does
At its core, a SIEM (Security Information and Event Management) platform does three things: collect logs and telemetry from many sources, normalize and correlate that data against known-bad patterns, and alert a human when something matches. A HIDS (Host-based Intrusion Detection System) narrows that scope to a single host: it watches log files, the filesystem, running processes, and configuration state for signs of compromise. Wazuh is both — agents provide the HIDS layer per host, and the manager/indexer/dashboard provide the SIEM layer across the whole fleet.
The decode → rule → alert pipeline
Every raw log line the manager receives goes through the same pipeline inside wazuh-analysisd:
- Decoding — a decoder (defined in XML) recognizes the log format (e.g.
sshd,sudo,iptables) and extracts structured fields likesrcip,user, andactionfrom the free-text line. - Rule matching— decoded fields are tested against a tree of rules. Rules can match on a single event (e.g. “failed password”) or on frequency/correlationacross multiple events (e.g. “8 failed passwords from the same IP within 120 seconds”).
- Alerting — a matched rule has a level (0-16, severity) attached. If the level clears the configured threshold, an alert is written to
alerts.json/alerts.logon the manager and shipped to the indexer for the dashboard to display.
Rule levels, at a glance
| Level | Meaning |
|---|---|
| 0 | Ignored — informational, no alert generated |
| 1-6 | Low severity — noise-prone, often just logged |
| 7-11 | Medium/high severity — actionable alerts (auth failures, policy violations) |
| 12-15 | High severity — likely active compromise indicators |
| 16 | Critical — reserved for the most severe, high-confidence detections |
MITRE ATT&CK mapping
Wazuh's default ruleset tags many rules with MITRE ATT&CK technique IDs (e.g. T1110 for brute force, T1098for account manipulation). This lets an analyst pivot from “I got an alert” straight to “this maps to the Credential Access tactic” without manually cross-referencing the framework, and lets you build dashboard visualizations grouped by tactic instead of by raw rule ID.
Architecture
A production Wazuh deployment has four moving parts. Agents run on every monitored endpoint and forward events over an encrypted channel to the manager. The manager decodes, correlates, and writes alerts, then hands them to Filebeat, which ships them to the indexer. The dashboard queries the indexer to render everything an analyst sees.
Agents → Wazuh Manager (analysisd) → Filebeat → Wazuh Indexer (OpenSearch) → Wazuh Dashboard
Component responsibilities
| Component | Role |
|---|---|
| Wazuh Agent | Runs on the monitored host; collects logs, monitors file integrity, and runs rootcheck/SCA checks |
| Wazuh Manager | Receives agent events, decodes and matches them against rules, generates alerts, manages agent enrollment and keys |
| Filebeat | Ships the manager's alert and archive JSON output into the indexer using the Wazuh Filebeat module |
| Wazuh Indexer | An OpenSearch cluster that stores and indexes alerts for fast search and aggregation |
| Wazuh Dashboard | An OpenSearch Dashboards build with Wazuh-specific plugins for browsing alerts, agents, and compliance views |
Single-node vs. distributed
For labs and small fleets, all four components can live on one host (the “all in one” install). Past a few hundred agents, split the indexer onto its own cluster of nodes — it is the component most sensitive to disk I/O and heap pressure.Installation
Wazuh ships an official quickstart script for small/all-in-one deployments, and a Docker Compose stack for distributed or disposable lab environments.
# Download and run the official install assistant
curl -sO https://packages.wazuh.com/4.9/wazuh-install.sh
sudo bash wazuh-install.sh -a
# The script installs and configures, in order:
# 1. Wazuh indexer (OpenSearch-based)
# 2. Wazuh manager (+ Filebeat for shipping alerts)
# 3. Wazuh dashboard (OpenSearch Dashboards)
#
# On completion it prints the generated admin password — save it immediately:
sudo tar -xvf wazuh-install-files.tar
# Verify all three services are active
sudo systemctl status wazuh-manager wazuh-indexer wazuh-dashboardvm.max_map_count before you start the indexer
OpenSearch refuses to boot unless the host kernel allows enough memory-mapped areas. Set this before bringing up the indexer container/service — see the Troubleshooting section for the exact fix.Configuration
Manager: ossec.conf essentials
The manager's behavior — which log sources to trust, active response, and global alert thresholds — lives in /var/ossec/etc/ossec.conf.
<ossec_config>
<global>
<jsonout_output>yes</jsonout_output>
<alerts_log>yes</alerts_log>
<logall>no</logall>
<email_notification>no</email_notification>
</global>
<alerts>
<log_alert_level>3</log_alert_level>
<email_alert_level>12</email_alert_level>
</alerts>
<remote>
<connection>secure</connection>
<port>1514</port>
<protocol>tcp</protocol>
</remote>
<!-- Vulnerability detection module -->
<vulnerability-detector>
<enabled>yes</enabled>
<interval>5m</interval>
<run_on_start>yes</run_on_start>
</vulnerability-detector>
<ruleset>
<decoder_dir>ruleset/decoders</decoder_dir>
<rule_dir>ruleset/rules</rule_dir>
<rule_dir>etc/rules</rule_dir>
<list>etc/lists/audit-keys</list>
</ruleset>
</ossec_config>Agent: ossec.conf essentials
Each agent's /var/ossec/etc/ossec.conf defines the manager address and which local log files and integrity paths to watch.
<ossec_config>
<client>
<server>
<address>10.0.0.10</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
<enrollment>
<enabled>yes</enabled>
<manager_address>10.0.0.10</manager_address>
<port>1515</port>
</enrollment>
</client>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/auth.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/nginx/access.log</location>
</localfile>
<syscheck>
<frequency>43200</frequency>
<directories check_all="yes" realtime="yes">/etc,/usr/bin,/usr/sbin</directories>
<ignore>/etc/mtab</ignore>
</syscheck>
<rootcheck>
<disabled>no</disabled>
</rootcheck>
</ossec_config>Registering an agent
Agent registration issues a unique key that both sides use to authenticate the encrypted channel. This can be done manually on the manager or automatically via wazuh-authd enrollment (used by the agent's <enrollment> block above).
# Manual registration
sudo /var/ossec/bin/manage_agents -a
# -> prompts for agent name and IP, then issues an ID
# Extract the key for a given agent ID to import on the agent
sudo /var/ossec/bin/manage_agents -e 003
# List all registered agents and their connection status
sudo /var/ossec/bin/agent_control -lAgent groups
Assign agents to groups (e.g.webservers, db-tier) via agent_groups -a -i <id> -g webservers so shared configuration (specific localfileblocks, syscheck paths) can be pushed centrally instead of hand-editing every agent's ossec.conf.Commands
wazuh-control — service lifecycle
sudo /var/ossec/bin/wazuh-control start
sudo /var/ossec/bin/wazuh-control stop
sudo /var/ossec/bin/wazuh-control restart
sudo /var/ossec/bin/wazuh-control status
# Or via systemd (preferred on both manager and agent)
sudo systemctl restart wazuh-manager
sudo systemctl restart wazuh-agentmanage_agents — key lifecycle on the manager
sudo /var/ossec/bin/manage_agents -a # add a new agent
sudo /var/ossec/bin/manage_agents -l # list agents
sudo /var/ossec/bin/manage_agents -e <id> # export/print the agent key
sudo /var/ossec/bin/manage_agents -r <id> # remove an agentAgent enrollment from the agent side
# Enroll using authd against the manager (fetches a key automatically)
sudo /var/ossec/bin/agent-auth -m 10.0.0.10
# Import a key generated manually on the manager instead
sudo /var/ossec/bin/manage_agents -i <base64-key-string>
# Restart the agent to pick up the new key
sudo systemctl restart wazuh-agentagent_control — status and force actions from the manager
sudo /var/ossec/bin/agent_control -l # list agents + status
sudo /var/ossec/bin/agent_control -i <id> # detailed info for one agent
sudo /var/ossec/bin/agent_control -R -u <id> # force-restart an agent remotelyExamples
The default ruleset (rule IDs roughly 1-99999) covers common software out of the box. Custom, environment-specific detections belong in /var/ossec/etc/rules/local_rules.xml using an ID range above 100000 so they never collide with vendor rule updates.
Identify the log source and confirm it's decoded
Tail/var/ossec/logs/archives/archives.json(requireslogall_json yes) to confirm the raw event arrives and which decoder matched it — you need the decoded field names before you can write a rule against them.Draft the rule against a known decoder
Choose a parent rule to inherit from (e.g. group"sudo"), then match on a specific field likefull_logusing a regex, keeping the rule as narrow as possible.Write it into local_rules.xml
Custom rules always live inlocal_rules.xml, never in the vendor ruleset files — those get overwritten on upgrade.Validate and reload
Run/var/ossec/bin/wazuh-logtestagainst a sample log line to confirm the rule fires at the intended level, thensystemctl restart wazuh-manager.
Example: alert when a user is added to the sudoers group
<group name="local,sudoers_change,">
<rule id="100010" level="10">
<if_sid>5902</if_sid>
<match>usermod.*-aG sudo|usermod.*-aG wheel</match>
<description>User added to sudo/wheel group</description>
<mitre>
<id>T1098</id>
</mitre>
<group>privilege_escalation,gdpr_IV_32.2,</group>
</rule>
</group>Rule 100010 extends base rule 5902 (the generic usermoddecoder match), narrows it with a regex on the command line, sets severity to level 10 (high), and tags it with the MITRE ATT&CK privilege escalation technique so it surfaces correctly in dashboard MITRE visualizations.
Screenshots
Wazuh Dashboard — Security Events view, showing alert volume by severity and top triggered rules
Screenshot placeholder
Wazuh Dashboard — Agents view, showing per-agent connection status, OS, and last keep-alive
Screenshot placeholder
Real World Example
Scenario: an internet-facing jump host is being brute-forced over SSH. Walk the full path from the raw auth log line to a dashboard alert.
SSH auth.log → agent logcollector → manager analysisd → correlated rule → indexer → dashboard
Attacker generates failed logins
Each failed attempt writes a line likeFailed password for root from 203.0.113.7 port 51422 ssh2to/var/log/auth.log(Debian/Ubuntu) or/var/log/secure(RHEL).Agent collects and forwards
The agent's<localfile>block for that path is tailed bywazuh-logcollectorand shipped to the manager over the encrypted 1514/tcp channel in near real time.Decoding and per-event matching
The built-insshddecoder extractssrcipanduser. A single failed attempt matches rule5710(“sshd authentication failed”, level 5) — on its own, too low to page anyone.Frequency correlation escalates it
Rule5720(“multiple authentication failures”) extends5710with a<frequency>/<timeframe>clause: 8 matches of the same rule group from the samesrcipwithin 120 seconds. When that threshold trips, the manager fires a level-10 correlated alert instead of eight separate low-level ones.Alert written and shipped
analysisdwrites the alert toalerts.jsonwith the full decoded context (source IP, rule ID, MITRE tagT1110, agent name). Filebeat's Wazuh module picks up new lines and bulk-indexes them into the indexer within seconds.Analyst sees it on the dashboard
The Security Events view surfaces the level-10 alert immediately; an analyst pivots onsrcipto see every other rule that IP has triggered fleet-wide, and can trigger an active response (e.g.firewall-drop) to block it automatically.
Common Issues
Agent shows 'Disconnected' in the dashboard
Check the agent's connection to the manager first with/var/ossec/bin/agent_control -i <id> on the manager, then confirm the agent process is actually running (systemctl status wazuh-agent) and that TCP 1514 is reachable — a stale or mismatched agent key after a manager reinstall is the most common cause.Wazuh indexer fails to start: vm.max_map_count too low
OpenSearch requires a higher virtual memory map count than the Linux default. Fix it withsudo sysctl -w vm.max_map_count=262144 and persist it by adding vm.max_map_count=262144 to /etc/sysctl.conf, then restart the indexer service.Self-signed certificate errors between manager/indexer/dashboard
The installer generates its own CA and node certificates under/etc/wazuh-indexer/certs and /etc/filebeat/certs. If any component was reinstalled independently, its certs no longer chain to the shared CA — regenerate all certificates from the same CA using the Wazuh certs tool rather than patching one node in isolation.Custom rule never triggers
Almost always a decoder mismatch: the rule's<if_sid> points at a parent rule that never actually matched the log line. Run /var/ossec/bin/wazuh-logtest with the exact raw log line to see which decoder and rule chain really fires before assuming your regex is wrong.Dashboard login or API authentication failures
The dashboard authenticates against the indexer, and the manager's REST API (port 55000) uses separate credentials from the indexer's admin account. Confirm you're resetting the right password with the correct tool —wazuh-passwords-tool for indexer/dashboard users vs. /var/ossec/bin/wazuh-apid user management for the manager API.Best Practices
- Keep every custom rule and decoder in
local_rules.xml/local_decoder.xml— never edit the shipped ruleset, which is overwritten on upgrade. - Use agent groups to centrally manage configuration for classes of hosts instead of hand-editing each agent's
ossec.conf. - Start new custom rules at a low level, confirm they fire correctly with real traffic, then raise the level once you trust the signal.
- Size the indexer for your alert volume up front — heap pressure and disk I/O on the indexer are the most common cause of dropped or delayed alerts at scale.
- Build MITRE ATT&CK-based dashboard visualizations, not just per-rule counts, so triage maps directly to attacker tactics.
Security Hardening
- File Integrity Monitoring — enable
syscheckwithrealtimeon sensitive paths (/etc, binaries directories) so unauthorized changes generate an immediate alert. - Tune, don't disable — a noisy rule usually needs a frequency/timeframe adjustment or a narrower match, not
<rule ... level="0">; disabling it blind to your environment can mask a real detection. - Manager-agent TLS — enrollment keys are unique per agent; rotate and revoke keys for decommissioned hosts immediately with
manage_agents -rrather than leaving stale registrations active. - Restrict dashboard/API access — put the dashboard behind a VPN or reverse proxy with its own auth layer, and scope manager API (55000/tcp) accounts to read-only where the caller doesn't need write access.
- Active response, cautiously — automated actions like
firewall-dropare powerful but can self-inflict outages on a false positive; pilot new active response rules in alert-only mode before letting them execute.
Interview Questions
Cheat Sheet
# --- Service control ---
sudo systemctl start|stop|restart|status wazuh-manager
sudo systemctl start|stop|restart|status wazuh-agent
sudo /var/ossec/bin/wazuh-control status
# --- Agent management (on manager) ---
sudo /var/ossec/bin/manage_agents -a # add agent
sudo /var/ossec/bin/manage_agents -l # list agents
sudo /var/ossec/bin/manage_agents -e <id> # export agent key
sudo /var/ossec/bin/manage_agents -r <id> # remove agent
sudo /var/ossec/bin/agent_control -l # list + status
sudo /var/ossec/bin/agent_control -i <id> # agent detail
# --- Agent enrollment (on agent) ---
sudo /var/ossec/bin/agent-auth -m <manager_ip>
sudo /var/ossec/bin/manage_agents -i <key>
# --- Rule/decoder testing ---
sudo /var/ossec/bin/wazuh-logtest
# --- Logs ---
tail -f /var/ossec/logs/ossec.log
tail -f /var/ossec/logs/alerts/alerts.json
# --- Indexer prerequisite ---
sudo sysctl -w vm.max_map_count=262144
# --- Config reload ---
sudo systemctl restart wazuh-managerSummary
Wazuh turns a fleet of otherwise-unrelated servers into a single detection surface: agents collect, the manager decodes and correlates against tunable rules, the OpenSearch-based indexer stores it all searchably, and the dashboard makes it visible. The real skill isn't installing the stack — it's understanding the decoder → rule → alert pipeline well enough to write narrow, MITRE-mapped custom rules in local_rules.xml, tune out noise instead of disabling coverage, and trace an alert like an SSH brute-force detection all the way back to the raw log line that triggered it.
Resources
- Official Wazuh documentation —
documentation.wazuh.com - Wazuh source code and rulesets on GitHub —
github.com - OpenSearch documentation (underlying the indexer/dashboard) —
opensearch.org - MITRE ATT&CK framework reference —
attack.mitre.org - Wazuh community forum —
wazuh.com/community