Why This Matters
Running Wazuh as a single node is fine for a lab or a small team. But in production, a single node is a liability — one failure and your entire security monitoring goes dark.
A Wazuh cluster gives you high availability, load distribution, and the ability to scale as your environment grows. This guide walks you through setting up a production-grade Wazuh cluster — step by step, no shortcuts.
What We Are Building
A production Wazuh cluster consists of three tiers:
Wazuh Agents (Endpoints)
↓
Wazuh Manager Cluster
├── Master Node → Handles configuration sync, API
└── Worker Node(s) → Handle agent connections, log collection
↓
Wazuh Indexer Cluster (OpenSearch)
├── Node 1
├── Node 2
└── Node 3
↓
Wazuh Dashboard (Single or HA)
Prerequisites
Before you start, make sure you have the following in place.
Minimum hardware for each node:
Operating System - Ubuntu 22.04 LTS / RHEL 8+
CPU - 4 vCPU
RAM - 8 GB (16 GB Recommended)
Disk - 100 GB SSD
Network - 1 Gbps, low latency
Server inventory for this guide:
Wazuh Master Node (wazuh-master) - 192.168.1.10
Wazuh Worker Node (wazuh-worker-01) - 192.168.1.11
Wazuh Indexer Node (wazuh-indexerr-01) - 192.168.1.20
Wazuh Indexer Node (wazuh-indexerr-02) - 192.168.1.21
Wazuh Indexer Node (wazuh-indexerr-03) - 192.168.1.22
Wazuh Dashboard (wazuh-dashboard) - 192.168.1.30
Adjust IPs and hostnames to match your environment.
Step 1 — Prepare All Nodes
Do this on every server before installing any Wazuh component.
1.1 Set Hostnames
bash
# On the master node
hostnamectl set-hostname wazuh-master
# On the worker node
hostnamectl set-hostname wazuh-worker-01
# Repeat for indexer and dashboard nodes with their respective names
1.2 Update /etc/hosts on All Nodes
Add all cluster node entries to /etc/hosts on every machine:
bash
192.168.1.10 wazuh-master
192.168.1.11 wazuh-worker-01
192.168.1.20 wazuh-indexer-01
192.168.1.21 wazuh-indexer-02
192.168.1.22 wazuh-indexer-03
192.168.1.30 wazuh-dashboard
1.3 Sync Time Across All Nodes
Wazuh clustering is sensitive to time drift. Use chrony or ntpd to keep all nodes in sync.
bash
# Install and enable chrony
apt install chrony -y # Ubuntu
# or
yum install chrony -y # RHEL/CentOS
systemctl enable --now chronyd
chronyc tracking
Make sure all nodes show the same reference time source.
Open the required ports on each node type.
On Wazuh Manager nodes:
bash
ufw allow 1514/tcp # Agent communication
ufw allow 1515/tcp # Agent enrollment
ufw allow 1516/tcp # Cluster communication
ufw allow 55000/tcp # REST API
On Wazuh Indexer nodes:
bash
ufw allow 9200/tcp # REST API (OpenSearch)
ufw allow 9300/tcp # Node-to-node cluster transport
On Wazuh Dashboard:
bash
ufw allow 443/tcp # HTTPS web UI
Step 2 — Generate SSL Certificates
Wazuh uses TLS for all internal communication. You generate certificates once and distribute them to all nodes.
Run this on any one node (you will copy certs to others):
bash
curl -sO https://packages.wazuh.com/4.x/wazuh-certs-tool.sh
curl -sO https://packages.wazuh.com/4.x/config.yml
2.2 Edit config.yml
This file tells the tool about all your nodes.
yaml
nodes:
indexer:
- name: wazuh-indexer-01
ip: "192.168.1.20"
- name: wazuh-indexer-02
ip: "192.168.1.21"
- name: wazuh-indexer-03
ip: "192.168.1.22"
server:
- name: wazuh-master
ip: "192.168.1.10"
node_type: master
- name: wazuh-worker-01
ip: "192.168.1.11"
node_type: worker
dashboard:
- name: wazuh-dashboard
ip: "192.168.1.30"
2.3 Generate the Certificates
bash
bash ./wazuh-certs-tool.sh -A
This creates a wazuh-certificates/ directory with individual cert archives for each node.
2.4 Distribute Certificates
Copy the correct cert package to each node:
bash
# Example: copy indexer certs to indexer node 1
scp wazuh-certificates/wazuh-indexer-01.tar root@192.168.1.20:/root/
Repeat for all nodes.
The Wazuh Indexer is an OpenSearch-based component. Set it up as a 3-node cluster for production resilience.
3.1 Add Wazuh Repository
Run on each indexer node:
bash
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --no-default-keyring \
--keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import
chmod 644 /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" \
| tee -a /etc/apt/sources.list.d/wazuh.list
apt-get update
3.2 Install Wazuh Indexer
bash
apt-get install wazuh-indexer -y
Edit /etc/wazuh-indexer/opensearch.yml on each indexer node.
On wazuh-indexer-01:
yaml
network.host: "192.168.1.20"
node.name: "wazuh-indexer-01"
cluster.initial_master_nodes:
- "wazuh-indexer-01"
- "wazuh-indexer-02"
- "wazuh-indexer-03"
discovery.seed_hosts:
- "192.168.1.20"
- "192.168.1.21"
- "192.168.1.22"
cluster.name: "wazuh-cluster"
node.max_local_storage_nodes: "3"
path.data: /var/lib/wazuh-indexer
path.logs: /var/log/wazuh-indexer
plugins.security.ssl.http.pemcert_filepath: /etc/wazuh-indexer/certs/wazuh-indexer-01.pem
plugins.security.ssl.http.pemkey_filepath: /etc/wazuh-indexer/certs/wazuh-indexer-01-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: /etc/wazuh-indexer/certs/root-ca.pem
plugins.security.ssl.transport.pemcert_filepath: /etc/wazuh-indexer/certs/wazuh-indexer-01.pem
plugins.security.ssl.transport.pemkey_filepath: /etc/wazuh-indexer/certs/wazuh-indexer-01-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: /etc/wazuh-indexer/certs/root-ca.pem
plugins.security.ssl.http.enabled: true
plugins.security.ssl.transport.enforce_hostname_verification: false
plugins.security.ssl.transport.resolve_hostname: false
plugins.security.authcz.admin_dn:
- "CN=admin,OU=Wazuh,O=Wazuh,L=California,C=US"
plugins.security.check_snapshot_restore_write_privileges: true
plugins.security.enable_snapshot_restore_privilege: true
plugins.security.nodes_dn:
- "CN=wazuh-indexer-01,OU=Wazuh,O=Wazuh,L=California,C=US"
- "CN=wazuh-indexer-02,OU=Wazuh,O=Wazuh,L=California,C=US"
- "CN=wazuh-indexer-03,OU=Wazuh,O=Wazuh,L=California,C=US"
plugins.security.restapi.roles_enabled:
- "all_access"
- "security_rest_api_access"
plugins.security.system_indices.enabled: true
plugins.security.system_indices.indices:
[".plugins-ml-model", ".plugins-ml-task", ".opendistro_security"]
Adjust network.host and node.name on indexer-02 and indexer-03 accordingly.
3.4 Deploy Certificates on Indexer Nodes
bash
# On each indexer node
NODE_NAME="wazuh-indexer-01" # change per node
mkdir /etc/wazuh-indexer/certs
tar -xf /root/${NODE_NAME}.tar -C /etc/wazuh-indexer/certs/ --wildcards --no-anchored \
"${NODE_NAME}*" "root-ca*" "admin*"
mv /etc/wazuh-indexer/certs/${NODE_NAME}/${NODE_NAME}.pem \
/etc/wazuh-indexer/certs/${NODE_NAME}.pem
mv /etc/wazuh-indexer/certs/${NODE_NAME}/${NODE_NAME}-key.pem \
/etc/wazuh-indexer/certs/${NODE_NAME}-key.pem
chmod 500 /etc/wazuh-indexer/certs
chmod 400 /etc/wazuh-indexer/certs/*
chown -R wazuh-indexer:wazuh-indexer /etc/wazuh-indexer/certs
3.5 Start and Initialize Wazuh Indexer
Start the service on all three indexer nodes:
bash
systemctl daemon-reload
systemctl enable wazuh-indexer
systemctl start wazuh-indexer
Then initialize the security plugin. Run this only once, from indexer-01:
bash
/usr/share/wazuh-indexer/bin/indexer-security-init.sh
Verify the cluster is healthy:
bash
curl -k -u admin:admin https://192.168.1.20:9200/_cluster/health?pretty
You should see "status": "green" and "number_of_nodes": 3.
4.1 Install Wazuh Manager on Both Nodes
Run on both master and worker:
bash
apt-get install wazuh-manager -y
Edit /var/ossec/etc/ossec.conf on the master node.
Find the <cluster> block and configure it:
xml
<cluster>
<name>wazuh</name>
<node_name>wazuh-master</node_name>
<node_type>master</node_type>
<key>c98b62a9cf5fe7beda35e0d5c05c6d1d9a77548f3d6b6d87a3cb0e23b3d5c8a2</key>
<port>1516</port>
<bind_addr>0.0.0.0</bind_addr>
<nodes>
<node>192.168.1.10</node>
</nodes>
<hidden>no</hidden>
<disabled>no</disabled>
</cluster>
Generate your own key with: openssl rand -hex 32
Also configure the Wazuh Indexer connection within ossec.conf:
xml
<indexer>
<enabled>yes</enabled>
<hosts>
<host>https://192.168.1.20:9200</host>
<host>https://192.168.1.21:9200</host>
<host>https://192.168.1.22:9200</host>
</hosts>
<ssl>
<certificate_authorities>
<ca>/etc/ssl/root-ca.pem</ca>
</certificate_authorities>
<certificate>/etc/ssl/filebeat.pem</certificate>
<key>/etc/ssl/filebeat-key.pem</key>
</ssl>
</indexer>
Edit /var/ossec/etc/ossec.conf on the worker node. Same cluster block, but with node_type set to worker:
xml
<cluster>
<name>wazuh</name>
<node_name>wazuh-worker-01</node_name>
<node_type>worker</node_type>
<key>c98b62a9cf5fe7beda35e0d5c05c6d1d9a77548f3d6b6d87a3cb0e23b3d5c8a2</key>
<port>1516</port>
<bind_addr>0.0.0.0</bind_addr>
<nodes>
<node>192.168.1.10</node>
</nodes>
<hidden>no</hidden>
<disabled>no</disabled>
</cluster>
The <key> must be identical on master and all workers.
4.4 Deploy Certificates on Manager Nodes
bash
# On each manager node
NODE_NAME="wazuh-master" # or wazuh-worker-01
mkdir /etc/ssl/
tar -xf /root/${NODE_NAME}.tar -C /etc/ssl/ --wildcards --no-anchored \
"${NODE_NAME}*" "root-ca*"
mv /etc/ssl/${NODE_NAME}/${NODE_NAME}.pem /etc/ssl/filebeat.pem
mv /etc/ssl/${NODE_NAME}/${NODE_NAME}-key.pem /etc/ssl/filebeat-key.pem
cp /etc/ssl/${NODE_NAME}/root-ca.pem /etc/ssl/root-ca.pem
chmod 400 /etc/ssl/filebeat.pem /etc/ssl/filebeat-key.pem /etc/ssl/root-ca.pem
4.5 Start Wazuh Manager on Both Nodes
bash
systemctl daemon-reload
systemctl enable wazuh-manager
systemctl start wazuh-manager
4.6 Verify Cluster Status
bash
/var/ossec/bin/cluster_control -l
Expected output:
NAME TYPE VERSION ADDRESS
wazuh-master master 4.x.x 192.168.1.10
wazuh-worker-01 worker 4.x.x 192.168.1.11
Step 5 — Install Wazuh Dashboard
5.1 Install the Package
bash
apt-get install wazuh-dashboard -y
5.2 Deploy Certificates
bash
mkdir /etc/wazuh-dashboard/certs
tar -xf /root/wazuh-dashboard.tar -C /etc/wazuh-dashboard/certs/ \
--wildcards --no-anchored "wazuh-dashboard*" "root-ca*"
mv /etc/wazuh-dashboard/certs/wazuh-dashboard/wazuh-dashboard.pem \
/etc/wazuh-dashboard/certs/wazuh-dashboard.pem
mv /etc/wazuh-dashboard/certs/wazuh-dashboard/wazuh-dashboard-key.pem \
/etc/wazuh-dashboard/certs/wazuh-dashboard-key.pem
chmod 500 /etc/wazuh-dashboard/certs
chmod 400 /etc/wazuh-dashboard/certs/*
chown -R wazuh-dashboard:wazuh-dashboard /etc/wazuh-dashboard/certs
Edit /etc/wazuh-dashboard/opensearch_dashboards.yml:
yaml
server.host: 0.0.0.0
server.port: 443
opensearch.hosts:
- https://192.168.1.20:9200
- https://192.168.1.21:9200
- https://192.168.1.22:9200
opensearch.ssl.verificationMode: certificate
opensearch.username: kibanaserver
opensearch.password: kibanaserver
opensearch.requestHeadersAllowlist: ["securitytenant","Authorization"]
opensearch_security.multitenancy.enabled: true
opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"]
opensearch_security.readonly_mode.roles: ["kibana_read_only"]
server.ssl.enabled: true
server.ssl.key: /etc/wazuh-dashboard/certs/wazuh-dashboard-key.pem
server.ssl.certificate: /etc/wazuh-dashboard/certs/wazuh-dashboard.pem
opensearch.ssl.certificateAuthorities:
- /etc/wazuh-dashboard/certs/root-ca.pem
uiSettings.overrides.defaultRoute: /app/wz-home
5.4 Start the Dashboard
bash
systemctl daemon-reload
systemctl enable wazuh-dashboard
systemctl start wazuh-dashboard
Access the dashboard at: https://192.168.1.30
Default credentials: admin / admin — change these immediately.
Step 6 — Enroll Wazuh Agents
6.1 Agent Enrollment Against the Cluster
For a clustered setup, agents should enroll via the master node and communicate through a load balancer or directly to the worker nodes.
For a simple setup without a load balancer, point agents at the master for enrollment and a worker for communication:
bash
# On the agent (Linux)
WAZUH_MANAGER="192.168.1.10" \
WAZUH_MANAGER_PORT="1514" \
WAZUH_REGISTRATION_SERVER="192.168.1.10" \
apt-get install wazuh-agent -y
systemctl daemon-reload
systemctl enable wazuh-agent
systemctl start wazuh-agent
6.2 Verify Agent Registration
On the master node:
bash
/var/ossec/bin/manage_agents -l
You should see your enrolled agent with status Active.
Step 7 — Production Hardening
7.1 Change Default Passwords
bash
# Change admin and kibanaserver passwords via OpenSearch Security plugin
curl -k -u admin:admin -X PUT \
"https://192.168.1.20:9200/_plugins/_security/api/internalusers/admin" \
-H "Content-Type: application/json" \
-d '{"password": "YourStrongPasswordHere!"}'
Update the new password in opensearch_dashboards.yml and restart the dashboard.
Create an ILM policy to manage index retention and rollover:
bash
curl -k -u admin:admin -X PUT \
"https://192.168.1.20:9200/_plugins/_ism/policies/wazuh-policy" \
-H "Content-Type: application/json" \
-d '{
"policy": {
"description": "Wazuh index lifecycle",
"states": [
{
"name": "hot",
"actions": [{"rollover": {"min_index_age": "1d", "min_size": "10gb"}}],
"transitions": [{"state_name": "delete", "conditions": {"min_index_age": "30d"}}]
},
{
"name": "delete",
"actions": [{"delete": {}}],
"transitions": []
}
]
}
}'
Adjust min_index_age and min_size to match your data volume requirements.
7.3 Enable Vulnerability Detection
In ossec.conf on the master node, enable the vulnerability detector:
xml
<vulnerability-detection>
<enabled>yes</enabled>
<index-status>yes</index-status>
<feed-update-interval>60m</feed-update-interval>
</vulnerability-detection>
Restart the manager:
bash
systemctl restart wazuh-manager
Add SMTP configuration to ossec.conf:
xml
<global>
<email_notification>yes</email_notification>
<email_to>secops@yourdomain.com</email_to>
<smtp_server>smtp.yourdomain.com</smtp_server>
<email_from>wazuh@yourdomain.com</email_from>
<email_maxperhour>10</email_maxperhour>
<email_alert_level>10</email_alert_level>
</global>
7.5 Tune Alert Levels
Review and tune rules to reduce false positives. Custom rules go in /var/ossec/etc/rules/local_rules.xml:
xml
<group name="local,syslog,">
<!-- Example: Ignore specific alerts -->
<rule id="100001" level="0">
<if_sid>5716</if_sid>
<srcip>192.168.1.0/24</srcip>
<description>Ignore SSH from trusted internal range</description>
</rule>
</group>
Step 8 — Verify Everything Is Running
Run these checks to confirm your cluster is healthy end to end.
bash
# Check Wazuh Manager cluster
/var/ossec/bin/cluster_control -l
# Check Wazuh Indexer cluster health
curl -k -u admin:admin https://192.168.1.20:9200/_cluster/health?pretty
# Check active agents
/var/ossec/bin/agent_control -l
# Check Wazuh Manager service
systemctl status wazuh-manager
# Check Wazuh Indexer service
systemctl status wazuh-indexer
# Check Wazuh Dashboard service
systemctl status wazuh-dashboard
All services should show active (running). The indexer cluster should show "status": "green".
Quick Reference — Important Paths
| ComponentConfig FileLog File |
| Wazuh Manager | /var/ossec/etc/ossec.conf | /var/ossec/logs/ossec.log |
| Wazuh Indexer | /etc/wazuh-indexer/opensearch.yml | /var/log/wazuh-indexer/ |
| Wazuh Dashboard | /etc/wazuh-dashboard/opensearch_dashboards.yml | /var/log/wazuh-dashboard/ |
| Agent | /var/ossec/etc/ossec.conf | /var/ossec/logs/ossec.log |
Wrapping Up
You now have a production-ready Wazuh cluster with:
- A 2-node manager cluster (master + worker) with shared cluster key
- A 3-node indexer cluster for resilience and performance
- TLS encryption across all communication
- Enrolled agents reporting to the cluster
- Basic production hardening in place
From here, you can scale by adding more worker nodes (just add them with the same cluster key), expand the indexer cluster, and layer in integrations like TheHive, MISP, or a SIEM ticketing workflow.
Comments (0)
Log in or register to leave a comment.
No comments yet. Be the first to comment!