IT Security

How to Setup Wazuh Cluster for Production Use

How to Setup Wazuh Cluster for Production Use

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

nano /etc/hosts


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.

1.4 Configure Firewall Rules

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.

2.1 Download the Wazuh Cert Tool

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.

Step 3 — Install and Configure Wazuh Indexer Cluster

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

3.3 Configure opensearch.yml

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.

Step 4 — Install and Configure Wazuh Manager Cluster

4.1 Install Wazuh Manager on Both Nodes

Run on both master and worker:

bash

apt-get install wazuh-manager -y

4.2 Configure ossec.conf — Master Node

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>

4.3 Configure ossec.conf — Worker Node

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

5.3 Configure opensearch_dashboards.yml

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 / adminchange 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.

7.2 Configure Index Lifecycle Management

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

7.4 Configure Email Alerts

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:

  1. A 2-node manager cluster (master + worker) with shared cluster key
  2. A 3-node indexer cluster for resilience and performance
  3. TLS encryption across all communication
  4. Enrolled agents reporting to the cluster
  5. 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.

Admin User

Written by Admin User

Content writer at UpBrightSkills

Comments (0)

No comments yet. Be the first to comment!