Skip to content
Wazuh Open-Source SIEM XDR Platform
Security Analytics

Wazuh: Multi-Node SIEM & XDR for Enterprise Security

A complete practitioner's guide to deploying Wazuh in a production multi-node architecture. Covers the Wazuh Server (Manager), Wazuh Indexer (OpenSearch-based), Wazuh Dashboard, and Wazuh Agent in depth - including cluster configuration, hardware sizing, compliance use cases, real-world threat detection scenarios, and TCO comparison against Splunk, IBM QRadar, and Microsoft Sentinel.

Pros

  • Completely free and open-source - zero licensing costs for unlimited agents and data ingestion
  • Unified SIEM + XDR platform: log analysis, FIM, SCA, vulnerability detection, and active response in one agent
  • Native MITRE ATT&CK mapping on every alert - immediate adversary tactic context for every detection
  • Multi-node cluster architecture scales horizontally to handle millions of events per day
  • Built-in compliance dashboards for PCI DSS, HIPAA, GDPR, NIST 800-53, TSC, and CMMC
  • 620,000+ community members and 10M+ Docker pulls - the most widely deployed open-source SIEM on the planet
  • Deep cloud integrations: AWS, Azure, GCP, Office 365, Docker, and Kubernetes monitoring out-of-the-box

Cons

  • No vendor-managed SaaS option - you own the infrastructure, patching, and scaling
  • Initial deployment and rule tuning requires strong Linux and security engineering skills
  • High EPS environments require careful Indexer cluster sizing and ongoing performance tuning
  • Custom decoder/rule authoring has a steep learning curve compared to commercial alternatives
  • No built-in SOAR - requires Shuffle or TheHive integration for automated response workflows

Ask a commercial SIEM vendor for a quote on 5,000 endpoints and the number that comes back is rarely the number you end up paying. The licence is indexed to ingestion volume, so every log source you add later is a fresh negotiation — which means the sources that did not fit the budget were chosen by procurement rather than by anyone in the SOC. That is the actual failure mode of expensive SIEM: not the invoice, but the visibility you quietly gave up to keep the invoice down.

Wazuh removes that constraint entirely. There is no licence, no per-agent fee, and no ingestion tier, so the question “should we collect this?” goes back to being an engineering question about disk and EPS rather than a procurement one. The community is large — Wazuh puts it north of 620,000 users — and the deployments behind those numbers include banks, hospitals, government departments and cloud-native startups.

What it does not remove is the work. Wazuh hands you the platform and keeps the operational burden: you size the Indexer cluster, you rotate the certificates, you tune the rules, and when the cluster goes yellow at 02:00 there is no vendor on the other end of an SLA. That trade is the whole decision, and it is worth making explicit before the first apt-get install.

This guide covers a proper multi-node deployment — cluster layout, hardware sizing that survives real EPS, the capabilities most teams never switch on, and the places where Wazuh fails silently rather than loudly.


What Is Wazuh, Really?

Wazuh is one platform doing the job that most organisations assemble from four. The log pipeline, the endpoint agent, the vulnerability scanner and the compliance checker all share a single agent, a single rule engine and a single index — which matters less for the feature list than for the correlation. A file change, the process that made it, the package inventory of the host and its CIS Benchmark score all arrive as fields on the same document, so you can ask “which of the hosts failing this control also had /usr/bin modified last week” without a join across three vendors.

Where it genuinely competes with Splunk and QRadar is detection content and breadth of collection. Where it does not is the polish around them: dashboards you would not build yourself, and a rule library that has already been tuned for someone else’s estate.

Log collection from everywhere — Agents on endpoints, syslog from network devices, cloud API polling (AWS, Azure, GCP), container logs from Docker and Kubernetes. Anything that can be tailed from a file or pulled from an API is in scope.

File Integrity Monitoring (FIM) — Detects file, directory and registry changes, and stores the diff rather than just the fact of the change. With whodata enabled it also tells you which process and which user did it, which is the difference between an alert and an answer.

Configuration assessment — CIS Benchmarks evaluated on-agent, with pass/fail/not-applicable scoring per control. Useful, and also a reliable way to generate hundreds of findings nobody has time to remediate — treat the first scan as a baseline exercise, not a work queue.

Vulnerability detection — Agents inventory installed packages and the server correlates them against CVE feeds. Note what this is: package-version matching, not exploitability. A backported vendor patch that leaves the version string unchanged will read as vulnerable, and the false positives concentrate on exactly the RHEL and Debian estates where backporting is standard practice.

Threat detection — Several thousand built-in rules with MITRE ATT&CK technique tags, so every alert arrives with the tactic already attached. The tags make coverage look measurable; resist the temptation to report a technique as covered because a rule mentions it, rather than because you have tested that the rule fires.

Automated response — Wazuh can drop the source IP on the host firewall, terminate a process, disable an account or run an arbitrary script. It will also, eventually, isolate the wrong host, so scope the responses that touch production before you enable them.

Cloud monitoring — Native modules for AWS CloudTrail, Azure Activity Logs, GCP Audit Logs and Office 365. All are polling integrations, which means the alert latency floor is the polling interval, not the event time.

Container and Kubernetes monitoring — Docker events and Kubernetes audit logs, giving you the layer most endpoint agents miss entirely.

Compliance dashboards — Pre-built modules for PCI DSS, HIPAA, GDPR, NIST 800-53, SOC 2 (TSC) and CMMC, mapped to rule IDs. What comes out is evidence an auditor will accept, not a claim that a control is satisfied.


How Wazuh Is Built

Wazuh splits into four components that scale independently. The reason to understand each one is not architectural tidiness — it is that each fails differently under resource pressure, and only one of those failures is loud. Starve the server of CPU and the analysis queue backs up with a visible warning. Starve the Indexer of disk and OpenSearch flips indices to read-only, at which point ingestion stops, no error surfaces in the dashboard, and the alert graphs simply flatten. A flat graph on a quiet Sunday looks exactly like a quiet Sunday.

Advertisement

The Wazuh Agent

The agent is a small C daemon you install on every endpoint. Idle, it sits in tens of megabytes of RAM and fractions of a percent of CPU — light enough that platform teams stop objecting once they have watched it for a week. The number that actually matters is not the idle footprint but the scan footprint: a recursive FIM scan with check_all over a large tree will hit disk hard and push memory several times above idle, which is why the scan window is configurable and why leaving it at the default on a busy database server is how you end up being asked to remove it.

Runs on basically everything:

  • Linux (Ubuntu, RHEL, CentOS, Debian, Amazon Linux, SUSE, and most things with glibc)
  • Windows (Windows 7 through Server 2022)
  • macOS, FreeBSD, OpenBSD, Solaris, AIX, HP-UX

The agent does the heavy lifting:

┌─────────────────────────────── Wazuh Agent ────────────────────────────────┐
│                                                                             │
│  Log Collection     File Integrity    System Inventory    Active Response   │
│  ─────────────      ──────────────    ────────────────    ───────────────   │
│  • /var/log/*       • FIM engine      • Running procs     • firewall-drop   │
│  • Windows EVT      • Real-time       • Packages/ports    • kill-process    │
│  • Syslog           • Scheduled scan  • OS fingerprint    • Custom scripts  │
│  • Application      • Audit rules     • HW inventory      • disable-user    │
│  • Custom files     • Diff storage    • Network ifaces    • quarantine      │
│                                                                             │
│  Security Configuration Assessment        Vulnerability Detector           │
│  ─────────────────────────────────        ────────────────────────         │
│  • CIS Benchmarks (Linux, Windows)        • Package → CVE correlation       │
│  • Pass/fail/NA scoring                   • NVD / OSV database queries      │
│  • Policy compliance %                    • CVSS scoring + severity         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                │  Encrypted TLS (port 1514)
                ▼
         [Wazuh Server]

Agent Resource Footprint:

  • CPU: < 0.5% average on modern hardware
  • RAM: ~20-40 MB idle, up to 150 MB during FIM scans
  • Network: ~1 Kbps per agent average (bursts during FIM/SCA)

The agent connects outbound to the server on 1514/TCP and receives configuration updates and active response commands back down the same channel, so no inbound rule to the endpoint is required. Enrolment runs over 1515/TCP separately, and it is worth being deliberate about it: the convenient path registers agents on request, which means anything that can reach that port can join your fleet and start submitting events. Restrict it, or pre-generate keys and close it once the rollout is done.

The failure mode to watch for is an agent that reports Active while shipping nothing useful. A wrong path in a <localfile> block, a log file the agent’s user cannot read, or a group configuration that never synced all produce the same result — a healthy-looking agent list and an empty query. Verify coverage by querying for events from each agent, not by reading the status column.


The Wazuh Server

The server is the analysis engine, and it is where almost every detection problem you will have actually lives. Agents ship raw text; the server decodes it into fields, tests those fields against the rule set, generates alerts, and hands them to Filebeat for storage.

The two-stage design — decode, then match — is the thing to internalise. A rule can only test fields a decoder produced. Point Wazuh at a log format with no matching decoder and it will not error; the events arrive, fail to decode, match nothing above rule level 0, and vanish. Your dashboards stay green and that log source contributes exactly nothing. Every custom rule you write should be validated with /var/ossec/bin/wazuh-logtest against a real sample line before it goes anywhere near production, because “the rule does not fire” and “the decoder never populated the field the rule tests” look identical from the dashboard.

Here’s the flow:

Agent logs come in
        │
        ▼
Gets decoded (raw text → structured fields)
        │
        ▼
Gets checked against 3,000+ detection rules
        │
        ▼
If a rule matches, an alert is generated
        │
        ▼
Alert is sent to the Indexer for storage
        ▼
If configured: Automatic response is triggered (block IP, kill process, etc.)

Key Processing Components:

ComponentFunction
Decoder Library3,000+ XML decoders that parse raw log text into structured fields (src_ip, user, action, event_id, etc.)
Rule Engine3,000+ detection rules with composable logic, frequency counters, correlation windows, and MITRE ATT&CK tags
CDB ListsHash-based lookup tables for known-bad IPs, malicious domains, whitelisted users - enable high-speed enrichment
Active Response DaemonExecutes firewall rules, process termination, and custom scripts on agent endpoints in response to triggered alerts
Cluster DaemonSynchronizes rule sets, decoders, CDB lists, and agent keys across all nodes in a Server cluster
FilebeatShips structured JSON alerts from the Server to the Wazuh Indexer over TLS

Custom Rule Example - Detecting LSASS Memory Access:

<rule id="100100" level="12">
  <if_group>windows</if_group>
  <field name="win.system.eventID">^10$</field>
  <field name="win.eventdata.targetImage" type="pcre2">(?i)lsass\.exe</field>
  <field name="win.eventdata.grantedAccess" type="pcre2">0x1410|0x1010</field>
  <description>Possible credential dumping - suspicious LSASS access detected</description>
  <mitre>
    <id>T1003.001</id>
  </mitre>
  <group>credential_access,lsass,windows</group>
</rule>

Hardware Sizing (Per Server Node):

EnvironmentAgentsEPSCPURAMDisk
SmallUp to 500< 5004 cores8 GB500 GB SSD
Medium500-3,000500-3,0008 cores16 GB1 TB SSD
Large3,000-10,0003,000-10,00016 cores32 GB2 TB SSD
Cluster10,000+10,000+Multiple nodes16 GB/node1 TB SSD/node

Treat these as starting points, not commitments. EPS is a poor predictor on its own, because rule complexity drives CPU as much as event volume does: a few dozen frequency-based correlation rules with long time windows cost more per event than several hundred simple field matches. Size on measured throughput from a pilot group of agents, then extrapolate.

One structural caveat on the cluster row. Wazuh server clustering distributes agent connections and synchronises configuration; it is not a highly available control plane. The master node owns rule, decoder and agent-key distribution, so losing it leaves workers analysing events with whatever configuration they last received while enrolment and configuration changes stop working. Alerting survives a master outage. Administration does not.


The Wazuh Indexer

The Indexer is the storage and search layer, and it is an OpenSearch cluster wearing a Wazuh label. That is the useful framing, because everything painful about running Wazuh at volume is an OpenSearch problem: shard counts, heap pressure, mapping conflicts, disk watermarks. Budget your operational learning here rather than on the rule syntax.

Retention is the decision with the longest tail. Whatever number you set is a hard ceiling on every future investigation — a 30-day window makes “when did they first get in” permanently unanswerable for any intrusion that started in week five, regardless of how good your analysts are. Dwell times routinely exceed a month, so 90 days is the floor worth arguing for, and the argument is about disk, which is cheap relative to an incident you cannot scope.

It stores:

  • Every alert Wazuh generates (in structured JSON format so you can query it)
  • Raw event archives (optional, and typically an order of magnitude larger than the alert index — enable them deliberately, per-source, not globally)
  • File integrity monitoring events (who changed what file, when, and how)
  • Vulnerability scan results
  • Compliance check results
  • Agent status and statistics

Index Architecture:

wazuh-alerts-4.x-YYYY.MM.DD       ← Daily alert index (primary search target)
wazuh-archives-4.x-YYYY.MM.DD     ← Raw event archive (optional, very high volume)
wazuh-monitoring-4.x-YYYY.MM.DD   ← Agent health and statistics
wazuh-statistics-4.x-YYYY.MM.DD   ← Server performance metrics

Multi-Node Cluster Configuration (config.yml):

# /etc/wazuh-indexer/opensearch.yml (Node 1 - Master-eligible)
network.host: 192.168.1.10
node.name: wazuh-indexer-1
cluster.name: wazuh-cluster

# Cluster peers
discovery.seed_hosts:
  - "192.168.1.10:9300"
  - "192.168.1.11:9300"
  - "192.168.1.12:9300"

cluster.initial_master_nodes:
  - "wazuh-indexer-1"
  - "wazuh-indexer-2"
  - "wazuh-indexer-3"

# Performance tuning
indices.query.bool.max_clause_count: 2048
thread_pool.search.queue_size: 10000

# Security
plugins.security.ssl.transport.pemcert_filepath: /etc/wazuh-indexer/certs/node.pem
plugins.security.ssl.transport.pemkey_filepath: /etc/wazuh-indexer/certs/node-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: /etc/wazuh-indexer/certs/root-ca.pem
plugins.security.ssl.http.enabled: true

Hardware Sizing (Per Indexer Node):

EnvironmentDaily AlertsRetentionCPURAMDisk (SSD)
Small< 1M90 days4 cores8 GB2 TB
Medium1M-10M90 days8 cores16 GB4 TB
Large10M-50M90 days16 cores32 GB8 TB
Enterprise50M+180 days16 cores × 5+ nodes32 GB/node16 TB/node

Critical: Use SSDs for Indexer nodes. Spinning disks will index acceptably and then fall over on search — an analyst running a 30-day aggregation is issuing exactly the random-read pattern HDDs are worst at, so the cluster looks healthy right up until someone tries to use it during an incident. Set JVM heap to 50% of RAM and keep it under 31 GB per node; above that threshold the JVM abandons compressed object pointers and you lose addressable heap by adding memory.

Two more Indexer behaviours are worth knowing before they find you. First, the disk watermarks: at 85% OpenSearch stops allocating new shards, and at 95% it flags existing indices read-only. Ingestion then stops silently — Filebeat retries, the dashboard renders, and the last alert you have is from whenever the disk filled. Alert on index disk usage and on daily alert volume dropping below its own baseline, because the absence of alerts is not observable by watching alerts. Second, field mappings are fixed for the life of an index. Send a field as a string on Monday and as an integer on Tuesday and the Tuesday documents are rejected, or the range query returns nothing at all — zero results, not an error, which reads identically to “no malicious activity”.


The Wazuh Dashboard

The Dashboard is an OpenSearch Dashboards instance with Wazuh’s plugin bolted on, and the pre-built views are the single biggest reason Wazuh is usable on week one rather than month three. Being handed working dashboards you did not author has a cost, though: analysts learn the views instead of the data model, and the first time a case needs a question the dashboard does not ask, nobody on the team knows how the index is shaped. Have at least one person who works in the Discover tab.

What you’ll see:

Overview — Live alert feed, severity breakdown, noisiest agents, most frequently firing rules. That last panel is the most useful thing on the page, and not for the reason people assume: it is your tuning backlog. Any rule in the top five that has never produced an escalation is training your analysts to click through alerts.

Security Events — Individual alerts, searchable and filterable, with full query support when the pre-built filters run out.

Integrity Monitoring — File changes by host and by time, with the stored diff attached. The timeline view is what turns “this binary changed” into “this binary changed four minutes after that SSH session”.

Vulnerability Detection — Findings across the estate ranked by severity. Rank remediation by exposure and exploitability rather than by CVSS alone; base scores deliberately encode nothing about whether the host is internet-facing.

Configuration Assessment — CIS Benchmark pass/fail per control per host. Expect the first scan to return a number large enough that people stop looking at it. Pick a subset, fix it, then widen.

MITRE ATT&CK — A heat map of techniques seen. Read it as a map of what your rules detect, not of what your adversaries do — the cold regions are usually missing telemetry rather than absent attackers.

Compliance Dashboards — PCI DSS, HIPAA, GDPR, NIST 800-53 and SOC 2 views, mapped to rule IDs and exportable as auditor-ready evidence. Worth naming the trade here: compliance reporting is very often what funds the SIEM, and platforms funded that way get tuned for evidence production rather than detection quality. Watch for that pull.

Cloud Security — AWS, Azure, GCP and Office 365 events in one place.

Agent Management — Enrolment, agent groups, and health. Group-based configuration is the only sane way to manage more than a few hundred agents; per-agent ossec.conf edits do not survive contact with a fleet.

Dashboard Configuration (wazuh.yml):

hosts:
  - default:
      url: https://wazuh-indexer-1
      port: 9200
      username: kibanaserver
      password: kibanaserver_password
      
# API connection to Wazuh Manager
wazuh:
  api:
    selector: true
    host: https://wazuh-server-1
    port: 55000
    username: wazuh-wui
    password: wazuh-wui-password
    run_as: false

The Production Architecture

Here is the layout most production deployments converge on. It handles roughly 5,000–15,000 agents depending on how chatty they are, and it tolerates the loss of any single Indexer node without interrupting ingestion.

                    ┌──────────────────────────────────────────┐
                    │           MONITORED ENDPOINTS             │
                    │  Windows Servers / Linux / macOS / Cloud  │
                    └──────────────┬───────────────────────────┘
                                   │  TLS 1514/TCP
                                   ▼
                    ┌──────────────────────────────┐
                    │      LOAD BALANCER (HAProxy)  │
                    │      (agent traffic)          │
                    └──────┬───────────────┬────────┘
                           │               │
               ┌───────────▼──┐     ┌──────▼───────────┐
               │   Wazuh       │     │    Wazuh          │
               │   Server 1    │     │    Server 2       │
               │  (Master)     │ ◄──► │   (Worker)       │
               └───────────────┘     └──────────────────┘
                   │  Filebeat TLS 9200            │
                   └──────────────┬────────────────┘
                                  │
           ┌──────────────────────┼──────────────────────┐
           │                      │                      │
  ┌────────▼──────┐   ┌───────────▼────┐   ┌────────────▼───┐
  │  Wazuh        │   │  Wazuh         │   │  Wazuh         │
  │  Indexer 1    │◄──►  Indexer 2     │◄──►  Indexer 3     │
  │  (Master)     │   │  (Data)        │   │  (Data)        │
  └───────────────┘   └────────────────┘   └────────────────┘
           │
  ┌────────▼──────┐
  │  Wazuh        │
  │  Dashboard    │
  │  (HTTPS 443)  │
  └───────────────┘

Why 3 Indexer Nodes? Quorum. Master election in OpenSearch needs a majority of master-eligible nodes, so three survives the loss of one and two survives the loss of none — a two-node cluster that loses a node has no majority and refuses to elect a master rather than risking split-brain. Three is the smallest count that buys you an actual node failure.

The part people skip: replicas. Three nodes give you a cluster that stays up; they do not give you data that survives a node loss unless the indices have at least one replica shard. Wazuh’s default index template is fine for that, but if anyone has tuned number_of_replicas down to zero to reclaim disk — and under storage pressure someone eventually does — you have converted a tolerable node failure into permanent loss of whichever shards lived there. Check it before you need it.

Note also what this diagram does not give you: the Dashboard is a single node, and so is the master server. Neither outage stops alerts being collected and indexed, which is the right thing to protect, but both stop you from doing anything about them. If analyst access during a failure matters, the Dashboard is the cheapest thing on this diagram to duplicate.


Deployment Guide: Step-by-Step Multi-Node Install

Step 1: Generate SSL/TLS Certificates

Do this first and do it carefully, because certificates are where most failed Wazuh deployments actually fail. Every link in the stack is TLS, the node names in the certificates must match the node names in the configuration exactly, and a mismatch surfaces as a cluster that will not form with an error several layers removed from the cause.

The other half of this step is the part nobody writes down: the generated certificates have an expiry. Whatever you produce today stops working in a year or two, on a day when nobody remembers running this script, and the symptom is agents dropping off and Filebeat refusing to ship. Put the expiry date in a calendar now, while you are looking at it.

# Download the certificate generation tool
curl -sO https://packages.wazuh.com/4.7/wazuh-certs-tool.sh
curl -sO https://packages.wazuh.com/4.7/config.yml

# Edit config.yml to define your node IPs
cat > config.yml << 'EOF'
nodes:
  indexer:
    - name: wazuh-indexer-1
      ip: 192.168.1.10
    - name: wazuh-indexer-2
      ip: 192.168.1.11
    - name: wazuh-indexer-3
      ip: 192.168.1.12

  server:
    - name: wazuh-server-1
      ip: 192.168.1.20
      node_type: master
    - name: wazuh-server-2
      ip: 192.168.1.21
      node_type: worker

  dashboard:
    - name: wazuh-dashboard
      ip: 192.168.1.30
EOF

# Generate all certificates
bash ./wazuh-certs-tool.sh -A

# This creates wazuh-certificates.tar with certs for all nodes
ls ./wazuh-certificates/

Step 2: Install and Configure Wazuh Indexer Cluster (All 3 Nodes)

# On each Indexer node (repeat with appropriate node name)
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --dearmor | \
  sudo tee /usr/share/keyrings/wazuh.gpg > /dev/null

echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] \
  https://packages.wazuh.com/4.x/apt/ stable main" | \
  sudo tee /etc/apt/sources.list.d/wazuh.list

sudo apt-get update && sudo apt-get install -y wazuh-indexer

# Deploy this node's certificates
NODE_NAME="wazuh-indexer-1"  # Change per node
tar -xf wazuh-certificates.tar -C /etc/wazuh-indexer/certs/ \
    ./${NODE_NAME}.pem ./${NODE_NAME}-key.pem ./admin.pem \
    ./admin-key.pem ./root-ca.pem

# Set permissions
chmod 500 /etc/wazuh-indexer/certs
chmod 400 /etc/wazuh-indexer/certs/*
chown -R wazuh-indexer:wazuh-indexer /etc/wazuh-indexer/certs

# Start and initialize
sudo systemctl enable --now wazuh-indexer

# After ALL 3 nodes are running, initialize the cluster (run once from node 1)
/usr/share/wazuh-indexer/bin/indexer-security-init.sh

Step 3: Install Wazuh Server Cluster

# On Wazuh Server 1 (Master)
sudo apt-get install -y wazuh-manager

# Configure cluster in /var/ossec/etc/ossec.conf
cat >> /var/ossec/etc/ossec.conf << 'EOF'
<cluster>
  <name>wazuh</name>
  <node_name>wazuh-server-1</node_name>
  <node_type>master</node_type>
  <key>c98b62a9b0169f24c0aef266747f7f2f</key>  <!-- 32-char random string -->
  <port>1516</port>
  <bind_addr>0.0.0.0</bind_addr>
  <nodes>
    <node>192.168.1.20</node>
    <node>192.168.1.21</node>
  </nodes>
  <hidden>no</hidden>
  <disabled>no</disabled>
</cluster>
EOF

# Install and configure Filebeat to forward alerts to Indexer
sudo apt-get install -y filebeat
curl -so /etc/filebeat/filebeat.yml \
  https://packages.wazuh.com/4.7/tpl/wazuh/filebeat/filebeat.yml

# Configure Indexer output in filebeat.yml
sed -i 's/YOUR_ELASTIC_SERVER_IP/192.168.1.10,192.168.1.11,192.168.1.12/' \
  /etc/filebeat/filebeat.yml

sudo systemctl enable --now wazuh-manager filebeat

Step 4: Install Wazuh Dashboard

sudo apt-get install -y wazuh-dashboard

# Configure /etc/wazuh-dashboard/wazuh.yml
cat > /etc/wazuh-dashboard/wazuh.yml << 'EOF'
hosts:
  - default:
      url: https://192.168.1.10
      port: 9200
      username: kibanaserver
      password: kibanaserver_password
EOF

# Configure /etc/wazuh-dashboard/opensearch_dashboards.yml
cat >> /etc/wazuh-dashboard/opensearch_dashboards.yml << 'EOF'
server.host: 192.168.1.30
server.port: 443
opensearch.hosts: ["https://192.168.1.10:9200"]
opensearch.ssl.verificationMode: certificate
EOF

sudo systemctl enable --now wazuh-dashboard

Step 5: Deploy Agents at Scale

# Linux agent deployment (one-liner for automation)
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --dearmor | \
  sudo tee /usr/share/keyrings/wazuh.gpg > /dev/null

echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] \
  https://packages.wazuh.com/4.x/apt/ stable main" | \
  sudo tee /etc/apt/sources.list.d/wazuh.list

WAZUH_MANAGER="192.168.1.20" WAZUH_AGENT_GROUP="linux-servers" \
  sudo apt-get install -y wazuh-agent

sudo systemctl enable --now wazuh-agent

# Windows agent deployment via PowerShell (for GPO/SCCM automation)
Invoke-WebRequest -Uri "https://packages.wazuh.com/4.x/windows/wazuh-agent-4.7.5-1.msi" `
  -OutFile "wazuh-agent.msi"

msiexec /i wazuh-agent.msi WAZUH_MANAGER="192.168.1.20" `
  WAZUH_AGENT_GROUP="windows-workstations" /qn /norestart

NET START WazuhSvc

Capability Deep Dive: File Integrity Monitoring

FIM is the capability most worth the configuration effort and the one most often left at defaults. The distinction that matters: it does not simply announce that a file changed, it stores the diff, and with whodata enabled it names the process and user responsible. “Something modified /etc/sudoers” is an alert. “vim running as www-data modified /etc/sudoers, and here is the added line” is a finding.

Two costs to plan for. Real-time monitoring consumes inotify watches on Linux, which are a finite per-user kernel resource — point realtime at a large tree and the agent will silently stop watching once the limit is hit, monitoring the first several thousand paths and nothing after that. Raise fs.inotify.max_user_watches or narrow the scope. And whodata depends on auditd, which means Wazuh is inserting audit rules on your hosts; on a busy filesystem that has a measurable performance cost, and it is the sort of cost that surfaces as a complaint from the database team rather than as anything in the Wazuh logs.

Scope FIM to what an attacker must touch — web roots, system binaries, cron directories, SSH authorised keys, registry Run keys — rather than to everything you would like to know about. A FIM policy generating a thousand daily alerts from application caches is worse than no FIM policy, because it also consumes the attention that would have caught the one that mattered.

Configuration Example - Protecting Web Server Root

<!-- In /var/ossec/etc/ossec.conf or agent group shared.conf -->
<syscheck>
  <!-- Real-time monitoring of web root -->
  <directories realtime="yes" report_changes="yes" check_all="yes">
    /var/www/html
  </directories>

  <!-- Scheduled monitoring of system binaries (no realtime needed - audit is better) -->
  <directories check_all="yes" check_sum="yes">
    /usr/bin,/usr/sbin,/bin,/sbin
  </directories>

  <!-- Monitor Windows registry for persistence mechanisms -->
  <windows_registry check_all="yes">
    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
  </windows_registry>

  <!-- Ignore noisy paths -->
  <ignore>/var/www/html/cache</ignore>
  <ignore type="sregex">\.log$</ignore>

  <!-- Still alert on changes, but never store the diff (avoids leaking secrets into alerts) -->
  <nodiff>
    /var/www/html/.htaccess
    /etc/passwd
    /etc/shadow
  </nodiff>

  <scan_time>02:00</scan_time>
  <scan_day>saturday</scan_day>
</syscheck>

What a FIM Alert Looks Like in TheHive/Dashboard:

  • Timestamp, agent, file path
  • Event type: added / modified / deleted
  • Previous checksum (MD5, SHA1, SHA256)
  • New checksum
  • Diff of file content (for text files)
  • File owner/permissions before and after
  • Process that made the change (via auditd integration)

That <nodiff> block is doing something specific and easy to misread. It does not suppress the alert — it suppresses the content of the diff. Use it on any file whose contents you do not want copied into an alert and then indexed for 90 days: /etc/shadow, private keys, application configuration holding database credentials. Without it, FIM will faithfully record the secret you rotated into a search index that more people can read than can read the original file.


Automated Response: Actually Stopping Attacks

Active Response is where Wazuh stops observing and starts acting. A rule fires, and within a second or so the agent on that host runs a script.

Be clear about what you are switching on. Active Response is arbitrary code execution on your endpoints, triggered by a rule, with no human between the detection and the action. Every one of your rules’ false positives now has a consequence.

What Wazuh can do automatically:

  • Block the attacker’s IP — Insert a host firewall rule dropping traffic from the source
  • Disable user accounts — Lock out an account that appears compromised
  • Kill malicious processes — Terminate a process that matched a rule
  • Isolate hosts — Null-route traffic from a compromised system
  • Run custom scripts — Anything you can script, Wazuh can execute

None of it is on by default, and that default is correct. The failure modes are specific and predictable. IP blocking keyed on authentication failures will eventually block your own NAT gateway, VPN concentrator or load balancer, taking out a population rather than an attacker — which is why firewall-drop on a shared egress address is a self-inflicted outage waiting for the right Monday. Account disablement triggered by a brute-force rule hands an attacker a denial-of-service primitive: they lock out whoever they like by failing to log in as them. And host isolation on a production database server is a decision with a business owner, not a rule ID.

There is a subtler cost too. Containment tips off the intruder. An automated block tells a patient operator that they have been seen, and some of them respond by burning access aggressively rather than quietly — which trades a contained incident you could have scoped for a fast one you cannot.

The version of this that works in practice: always set a <timeout> so blocks expire on their own, keep an explicit allow-list of infrastructure addresses that must never be dropped, start with <location>local</location> so responses stay on the affected host rather than propagating fleet-wide, and route anything touching accounts or production availability through a human approval gate in your SOAR rather than through Wazuh directly. Automate the reversible actions. Escalate the rest.

Custom Active Response Example - Block SSH Brute Force

<!-- Define the command -->
<command>
  <name>firewall-drop-ssh-brute</name>
  <executable>firewall-drop.sh</executable>
  <timeout_allowed>yes</timeout_allowed>
</command>

<!-- Link command to rule trigger -->
<active-response>
  <command>firewall-drop-ssh-brute</command>
  <location>local</location>          <!-- Execute on the agent being attacked -->
  <rules_id>5763</rules_id>           <!-- Wazuh rule: SSH auth failure x10 -->
  <timeout>3600</timeout>             <!-- Auto-unblock after 1 hour -->
  <repeated_offenders>30,60,120</repeated_offenders>  <!-- Escalating timeouts -->
</active-response>

Compliance: The Thing Auditors Actually Care About

Auditors do not ask whether you have a SIEM. They ask you to demonstrate, for a named control, on a named system, over a named period, that you were monitoring — and the usual answer is three weeks of someone assembling screenshots. Wazuh’s compliance modules exist to make that an export.

Every major framework has a mapped dashboard:

PCI DSS — Requirements mapped to rule IDs. FIM covers requirement 10 (logging and monitoring of access), configuration assessment covers requirement 2 (hardened builds), detection rules cover anomalous activity.

HIPAA — Access monitoring for systems holding protected health information, audit trails for administrative activity, and breach-relevant detection rules.

GDPR — Data access tracking, unauthorised modification detection, integrity verification.

NIST 800-53 — Controls mapped across identify, protect, detect, respond and recover.

SOC 2 Type II — Security, availability, confidentiality and processing integrity criteria.

CMMC Level 2 — Access control, audit logging and incident response requirements for defence contractors.

Two honest caveats, because this is where SIEM marketing does the most damage. A mapping is not a satisfied control: Wazuh produces evidence towards a requirement, and whether the requirement is met depends on scope — a PCI mapping means nothing if the agents are not deployed across the cardholder data environment, and the dashboard will happily show a healthy green matrix for the 60% of the estate you are collecting from. And the dashboards report on rule coverage, not on whether the rules fire; a control evidenced by a rule that has never triggered because its decoder never matched is the exact gap an assessor is paid to find.

Use the export as a starting artefact and check its scope against your asset inventory before it goes anywhere near an auditor.

PCI DSS Requirement 10 in Practice

PCI DSS Requirement 10 mandates logging and monitoring of all access to system components and cardholder data. Wazuh addresses this directly:

<!-- Enable audit logging for all root commands (PCI DSS 10.2.2) -->
<localfile>
  <log_format>audit</log_format>
  <location>/var/log/audit/audit.log</location>
</localfile>

<!-- Monitor all access to the cardholder data directory (PCI DSS 10.2.1) -->
<syscheck>
  <directories realtime="yes" check_all="yes" report_changes="yes" 
               whodata="yes">
    /var/data/cardholder
  </directories>
</syscheck>

The Dashboard’s PCI DSS module then renders a requirements matrix with per-sub-requirement status you can export directly.

Note whodata="yes" in that block — without it you get “this file was read” and not “this account read this file”, and for requirement 10.2.1 the identity is the entire point. Note also that this is the configuration most likely to be quietly reverted: audit-backed real-time monitoring on a high-throughput data directory has a real performance cost, and the person who turns it off to fix a latency complaint will not think of it as changing a compliance control.


The Real Cost: What You Actually Pay

Licensing Alone (5,000 Endpoints, 3 Years)

These are indicative figures, not quotes. Commercial SIEM pricing is negotiated, volume-indexed and usually under NDA, so treat the columns below as an order of magnitude and the ratios as the point. Your own numbers will differ by a wide margin depending on ingestion volume, region, contract length and how badly the vendor wants the logo.

PlatformYear 13-Year Total
Wazuh$0$0
Splunk Enterprise Security$240,000$720,000
IBM QRadar$180,000$540,000
Microsoft Sentinel$85,000 (+ growth)$285,000+

The licensing-only comparison is also the least useful one, and it is the one every open-source advocate leads with. Wazuh’s zero is real, but it is a zero in one column of a spreadsheet that has other columns.

Full Cost of Ownership (Including Everything)

Licensing plus infrastructure plus the person who runs it:

What You’re Paying ForWazuh Year 1Commercial Equivalent
Licensing$0$180K-$240K
Servers (buy/lease)$18,000Included in SaaS
Initial setup (one-time)$15K-$25KIncluded in implementation
Someone to run it (0.5 FTE)$40K-$60K$80K-$120K (they also need ops help)
Support (optional)$12,000Included
Year 1 Total$85,000-$115,000$300,000-$450,000

The gap is large enough to survive a lot of disagreement about the inputs — but the 0.5 FTE line is where these comparisons usually go wrong, in both directions. Under-count it and you have described a deployment that decays: rules go untuned, certificates expire, the cluster runs on a version two years behind, and eighteen months later someone concludes that open-source SIEM does not work. Over-count it and you have argued yourself into a commercial platform that also needs an engineer, because SaaS SIEM does not tune its own detections either.

The honest framing is that Wazuh converts a licence into a headcount. That is a good trade when you can hire the person, and a bad one when the role is a fraction of someone’s job alongside three other systems. It is also a different kind of risk: a licence renewal is predictable and a key-person dependency is not, so if your entire Wazuh knowledge lives with one engineer, budget for the second one before you count the savings.

On that basis the three-year total lands well below Splunk or QRadar and modestly below Sentinel, with the caveat that Sentinel’s gap narrows sharply if you are already paying for the Microsoft licensing that subsidises its ingestion.


Real Story: How a Healthcare Organization Fixed Their Audit Finding

What follows is a composite scenario, not a named engagement — the shape is drawn from how these migrations typically run, and the figures are illustrative rather than quoted. It is included because the failure it describes is extremely common and rarely written down.

The setup: a regional healthcare provider, three hospitals and a dozen clinics, running a commercial SIEM covering roughly a fifth of its endpoints. The gap was not a licensing decision anyone made deliberately. It was the predictable end state of volume-based pricing — coverage was extended until the invoice hit the ceiling, and then it stopped, and the boundary between monitored and unmonitored hosts settled wherever the budget ran out. Nobody chose which 1,400 endpoints went dark. The renewal did.

The HIPAA finding was the consequence: no comprehensive log collection, no file integrity monitoring on systems handling patient data, and no way to expand either without a contract renegotiation the organisation could not fund.

The Problem

MetricCommercial SIEM (Before)
Endpoints covered400 of 1,800 (22%)
Annual licensing cost$160,000
FIM coverage0 hosts (add-on not licensed)
Compliance dashboardPartial HIPAA only
MITRE ATT&CK mappingNot available
Cloud monitoring (Azure AD, Office 365)Not included

The Migration to Wazuh

A six-node deployment, sized for full-estate coverage rather than for what the previous licence allowed:

  • 3 Indexer nodes (8 cores / 16 GB RAM / 4 TB SSD each)
  • 2 Server nodes (8 cores / 16 GB RAM each) behind HAProxy
  • 1 Dashboard node (4 cores / 8 GB RAM) with Nginx reverse proxy

90-day migration timeline:

  1. Days 1-14: Infrastructure deployment, certificate generation, cluster initialization
  2. Days 15-30: Agent rollout to all 1,800 endpoints via SCCM (Windows) and Ansible (Linux/RHEL)
  3. Days 31-60: Rule tuning, custom decoder authoring for Epic EHR and Meditech logs, HIPAA dashboard validation
  4. Days 61-90: Active Response configuration, FIM policy deployment, compliance baseline generation

The ordering here is deliberate and worth copying. Rule tuning sits before the compliance baseline, because a baseline generated against an untuned rule set bakes in every false positive as a permanent feature of your reporting. The custom decoder work for the clinical systems is also the part that always overruns — EHR platforms emit proprietary log formats that no upstream decoder covers, and until someone writes one, those events arrive and match nothing. That silent gap is the single most likely way a migration like this ships with the original audit finding intact.

The Results

MetricBefore (Commercial)After (Wazuh)Change
Endpoints covered400 (22%)1,800 (100%)+350%
Annual licensing cost$160,000$0-100%
FIM coverage0 hosts1,800 hostsNew capability
HIPAA dashboardPartialFull (12 controls)Complete
MITRE ATT&CK visibilityNoneFull TTP mappingNew capability
Azure AD / O365 monitoringNot includedFull coverageNew capability
CVSS vulnerability visibilityNot includedAll hostsNew capability
Infrastructure cost (annual)Included in licensing$18,000-
Alert-to-triage time (avg)47 minutes12 minutes-75% (via Shuffle integration)

The interesting line is not the saved licence. It is where the money went: a dedicated operations engineer first, infrastructure second, and only then the discretionary security programme. That order is the whole reason the deployment survived past year one — the migrations that fail are the ones where the entire licence saving is reallocated to something visible and the platform is left as a fraction of someone’s week.

Read the coverage row as the real result. Going from a fifth of the estate to all of it is a bigger security change than any single capability in the table, and it happened because the marginal cost of the next thousand agents was disk rather than a purchase order. The alert-to-triage improvement, by contrast, is not really a Wazuh number — it comes from the SOAR layer sitting on top, and quoting it as a SIEM benefit is the kind of attribution error that makes the next project’s business case unfalsifiable.

Worth stating what got harder. The team now owns certificate rotation, version upgrades, cluster capacity and decoder maintenance for the clinical estate, and there is nobody to escalate to at 02:00. The audit finding closed. The operational surface grew.


Wazuh Doesn’t Live Alone

Wazuh has a specific gap, and it is worth naming rather than working around: there is no case management and no orchestration. Alerts land in a dashboard and stop there. Which means that without something downstream, the human workflow is a person watching a feed and copying indicators into a browser — and that is precisely the work that does not survive a 450-alert day.

The Full Open-Source SOC Stack

Wazuh detects. TheHive holds the case, Cortex enriches the observables, Shuffle decides what happens next:

[Wazuh Agents] ──► [Wazuh Server] ──► [Wazuh Indexer]
                          │
                    [Alert Webhook]
                          │
                          ▼
                    [Shuffle SOAR]
                          │
          ┌───────────────┼──────────────────┐
          ▼               ▼                  ▼
    [Cortex:         [TheHive:          [Slack/Teams:
    Enrich IOCs]    Create Case]        Notify SOC]
          │               │
          └───────────────┘
               Pre-triaged
               Case with full
               intelligence

Wazuh + MISP (Threat Intelligence)

<!-- Custom integration to pull MISP IoCs into Wazuh CDB lists -->
<integration>
  <name>misp</name>
  <hook_url>http://misp-server/api/attributes/restSearch</hook_url>
  <api_key>YOUR_MISP_API_KEY</api_key>
  <alert_format>json</alert_format>
</integration>

Feeding MISP indicators into CDB lists gives you hash-table lookups at ingestion speed, which is the right way to do intelligence matching — a rule referencing a CDB list costs almost nothing per event, whereas an API call per observable does not survive production EPS. The cost is freshness: a CDB list is a snapshot, so it is only as current as the last sync, and a sync job that has been failing quietly for a fortnight leaves you matching last month’s indicators with no error anywhere.

Wazuh + Velociraptor (Digital Forensics)

Wazuh’s active response can trigger Velociraptor artefact collection on the endpoint that generated the alert, so volatile evidence is captured while it still exists rather than after someone gets round to the ticket. This is the highest-value automation in the whole stack, because it is purely additive — collecting memory and process artefacts changes nothing about the host’s availability, so it needs none of the approval gates that blocking and isolation do. Automate collection aggressively. Automate containment carefully.


Security Hardening: Production Best Practices

A SIEM is a high-value target for the obvious reason — it holds the record of everything an intruder did — and for a less obvious one. Wazuh can execute commands on every agent in the estate. An attacker with server API access does not merely read your telemetry; they inherit a fleet-wide remote execution channel that your own security team installed and your monitoring trusts. Harden it accordingly.

1. Harden the API

# Change default API credentials immediately
curl -k -X PUT "https://localhost:55000/security/users/1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"password": "YourStrongPassword123!"}'

# Restrict API access to Dashboard IP only (firewall rule)
ufw allow from 192.168.1.30 to any port 55000
ufw deny 55000

2. Agent Authentication with Keys

# Pre-generate agent keys for unattended enrollment
/var/ossec/bin/manage_agents -a  # Add agent, generates unique AUTH key
/var/ossec/bin/manage_agents -e  # Export key for distribution

# Enforce agent key authentication (blocks any unauthorized agent)
# In ossec.conf:
# <auth><use_source_ip>yes</use_source_ip></auth>

3. Index Template for Data Retention

# Set 90-day index lifecycle policy via API
curl -k -X PUT "https://localhost:9200/_plugins/_ism/policies/wazuh-alerts-policy" \
  -H "Content-Type: application/json" \
  -u admin:admin -d '{
    "policy": {
      "phases": {
        "hot": { "min_index_age": "1d" },
        "delete": { 
          "min_index_age": "90d",
          "actions": { "delete": {} }
        }
      }
    }
  }'

Two things about that policy. The -u admin:admin is the default credential and it must not survive your first week; the Indexer’s admin account can read every alert you have ever generated, including the FIM diffs of files you would not grant those people access to directly. And a delete phase is irreversible in the way people underestimate — the day you discover an intrusion started 100 days ago is the day this policy decides how much of it you can reconstruct. Set the retention window against your worst plausible dwell time, not against your current disk.


Is Wazuh the Right Choice?

Perfect Fit

MSSPs and MDR providers — Per-endpoint licensing is the tax that makes small clients unprofitable, and Wazuh does not charge it, so the marginal cost of the next client is infrastructure. Note the real constraint you inherit instead: tenant separation is something you architect, usually with separate clusters or rigorous agent grouping and index-level access control. Wazuh will not stop you showing one client another client’s alerts. Your design has to.

Healthcare and financial services — HIPAA and PCI DSS dashboards ship with the platform and the exports hold up in an audit, provided your agent coverage actually spans the regulated scope.

Government and defence contractors — Full data sovereignty. Nothing leaves your network, there is no vendor telemetry to justify to a security review, and CMMC and NIST 800-53 are natively mapped. For air-gapped environments this is frequently the only viable option rather than merely the cheapest.

Organisations that scale fast — Doubling your endpoint count is a capacity exercise, not a contract renegotiation. That single property is what stops coverage decisions being made by procurement.

Teams with Linux and DevOps depth — If your team is comfortable with clustering, certificates and JVM tuning, Wazuh is a natural fit. If you deploy it with Ansible and manage its configuration in version control, it is a genuinely pleasant system to run.

Look Elsewhere If…

You have no Linux depth — Wazuh expects you to diagnose an OpenSearch cluster in yellow state, reason about shard allocation and rotate TLS certificates before they expire. Without that, you will get a working deployment and then a slowly rotting one. A managed platform is more expensive and much harder to accidentally neglect.

You want pre-built everything — Splunk Enterprise Security ships with a large tuned content library. Wazuh gives you a competent baseline and expects you to author decoders for anything proprietary, which for a bespoke or clinical estate is real engineering work on the critical path.

Your entire stack is Microsoft 365 and Azure — Sentinel’s identity and Graph telemetry integration goes deeper than any external SIEM can reach, and if the Microsoft licensing is already sunk, its effective cost is far lower than a list-price comparison suggests.

You need a vendor on the phone at 02:00 — Open-source Wazuh has no SLA. Wazuh Cloud and paid support exist, but the distinction matters: you are hiring support rather than having it bundled into a licence, and if you are the only organisation running your particular configuration, an escalation still ends with your engineer reading the source.

Your SIEM budget is protecting a headcount you cannot get — This is the honest disqualifier and it is rarely listed. If nobody can be assigned to own the platform, a licence that includes operations is the better purchase, and the fact that it costs more is the point.


The Real Story

The best argument for Wazuh is not the price. It is that the price stops shaping your architecture.

When ingestion is metered, every design conversation quietly becomes a cost conversation. Do we collect DNS logs? Do we keep 90 days or 30? Do we extend the agent to the clinical VLAN? Those are security questions with security answers, and volume-based licensing turns all of them into procurement questions answered by whoever holds the budget. Remove the meter and they go back to being decisions your engineers can make on the evidence.

What you get in exchange for the licence is a real platform — FIM with diffs and process attribution, on-agent CIS assessment, package-level vulnerability correlation, cloud and container collection, and response actions that execute in about a second. Not a reduced-functionality edition of something better.

What you give up is someone else’s operational responsibility. You own the cluster, the certificates, the upgrades, the decoders for anything proprietary, and the 02:00 call. Wazuh converts a predictable annual invoice into a headcount and a set of skills, and that trade is excellent right up until the person holding those skills resigns.

Over three years at 5,000 endpoints the all-in gap against Splunk or QRadar is comfortably into six figures. But the saving is not the win — the saving is the funding. It buys the engineer who tunes the rules, the retention window that lets you scope an intrusion properly, and the SOAR layer that turns detections into responses. Spend it on the platform and it compounds. Spend it elsewhere and in eighteen months you will have a neglected cluster and a story about how open-source SIEM does not work.


Resources & Further Reading

ResourceDescription
Official Wazuh DocumentationDeployment guides, API reference, rule authoring
Wazuh GitHubSource code, issue tracker, community contributions
Wazuh Community SlackWhere troubleshooting actually happens — faster than the issue tracker for configuration problems
Wazuh BlogIntegration tutorials, detection engineering, use case guides
Wazuh + TheHive IntegrationOfficial guide to connecting Wazuh alerts to TheHive cases
Wazuh + Shuffle SOARCommunity SOAR playbook templates for Wazuh + Shuffle
CIS BenchmarksHardening guides used by Wazuh SCA policies
MITRE ATT&CKAdversary tactic and technique reference

Share article

Subscribe to my newsletter

Receive my case study and the latest articles on my WhatsApp Channel.

Warning

Ask CyberROX AI