Wazuh is a security monitoring platform providing SIEM and XDR capabilities. It includes manager, indexer, dashboard, and agent components that store and process sensitive security telemetry. This guide covers security measures for production Wazuh deployments.
Wazuh architecture includes multiple security-sensitive components:
Key security concerns include agent authentication, API access control, sensitive data protection, and securing active response mechanisms.
Configure firewall rules for Wazuh components:
# Wazuh Manager
ufw allow from 10.0.0.0/8 to any port 1514 proto tcp # Syslog
ufw allow from 10.0.0.0/8 to any port 1514 proto udp # Syslog
ufw allow from 10.0.0.0/8 to any port 1515 proto tcp # Agent enrollment
ufw allow from 10.0.0.0/8 to any port 1516 proto tcp # Agent communication
# Wazuh Dashboard (via Nginx)
ufw allow from 10.0.0.0/8 to any port 443 proto tcp
# Wazuh Indexer
ufw allow from 10.0.0.0/8 to any port 9200 proto tcp # HTTP API
ufw allow from 10.0.0.0/8 to any port 9300 proto tcp # Transport
# Block external access
ufw deny from any to any port 1514 proto tcp
ufw deny from any to any port 1515 proto tcp
ufw deny from any to any port 1516 proto tcp
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: wazuh-network-policy
spec:
podSelector:
matchLabels:
app: wazuh-manager
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 1515
- protocol: TCP
port: 1516
Configure Wazuh Manager binding:
<!-- /var/ossec/etc/ossec.conf -->
<remote>
<connection>secure</connection>
<port>1514</port>
<protocol>tcp</protocol>
<allowed-ips>10.0.1.0/24</allowed-ips>
<allowed-ips>10.0.2.0/24</allowed-ips>
</remote>
<remote>
<connection>syslog</connection>
<port>1514</port>
<protocol>udp</protocol>
<allowed-ips>10.0.3.0/24</allowed-ips>
</remote>
Configure Wazuh API binding:
# /var/ossec/api/configuration/api.yaml
host: 127.0.0.1
port: 55000
use_only_authd: true
drop_privileges: true
https:
enabled: yes
key: /var/ossec/api/configuration/ssl/server.key
cert: /var/ossec/api/configuration/ssl/server.crt
Configure native authentication:
# /etc/wazuh-dashboard/opensearch_dashboards.yml
opensearch_security.auth.type: "basicauth"
opensearch_security.auth.anonymous_auth_enabled: false
Create users via Wazuh Dashboard or API:
# Create user via API
curl -k -X POST "https://localhost:55000/security/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "analyst",
"password": "${USER_PASSWORD}",
"roles": ["analyst"]
}'
Configure Wazuh RBAC:
# Create custom role
curl -k -X POST "https://localhost:55000/security/roles" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "soc_analyst",
"rule": {
"allow": {
"agent": ["read"],
"decoder": ["read"],
"rule": ["read"],
"security": ["read"],
"overview": ["read"],
"agents": ["read"],
"management": ["read"]
},
"deny": {
"agent": ["create", "update", "delete"],
"manager": ["*"]
}
}
}'
Default roles:
Configure agent enrollment security:
# Generate agent key on manager
/var/ossec/bin/manage-agents -a <agent_id> -n <agent_name> -I <agent_ip>
# Or use auto-enrollment with secure registration
# /var/ossec/etc/ossec.conf (manager)
<auth>
<disabled>no</disabled>
<port>1515</port>
<use_source_ip>no</use_source_ip>
<purge>yes</purge>
<use_password>yes</use_password>
<ciphers>HIGH:!ADH:!EXP:!MD5:!RC4:!3DES:!CAMELLIA:@STRENGTH</ciphers>
<ssl_verify_host>yes</ssl_verify_host>
<ssl_manager_cert>/var/ossec/etc/sslmanager.cert</ssl_manager_cert>
<ssl_manager_key>/var/ossec/etc/sslmanager.key</ssl_manager_key>
<ssl_auto_negotiate>yes</ssl_auto_negotiate>
</auth>
Agent configuration:
<!-- /var/ossec/etc/ossec.conf (agent) -->
<client>
<server>
<address>10.0.1.100</address>
<port>1515</port>
<protocol>tcp</protocol>
</server>
<config-profile>centos, centos8</config-profile>
<notify_time>10</notify_time>
<time-reconnect>60</time-reconnect>
<auto_restart>yes</auto_restart>
<crypto_method>aes</crypto_method>
</client>
<client_buffer>
<queue_size>5000</queue_size>
<events_per_second>500</events_per_second>
</client_buffer>
Configure SAML authentication for Dashboard:
# /etc/wazuh-dashboard/opensearch_dashboards.yml
opensearch_security.auth.type: "saml"
opensearch_security.ui.openid.login.buttonname: "Login with SSO"
opensearch_security.ui.openid.login.brandimage: "https://sso.company.com/logo.png"
opensearch_security.openid.connect_url: "https://sso.company.com/.well-known/openid-configuration"
opensearch_security.openid.client_id: "wazuh-dashboard"
opensearch_security.openid.client_secret: "${OIDC_CLIENT_SECRET}"
opensearch_security.openid.base_redirect_url: "https://wazuh.company.com"
Enable multi-factor authentication:
# /etc/wazuh-dashboard/opensearch_dashboards.yml
opensearch_security.multifactor.enabled: true
opensearch_security.multifactor.mode: "optional" # or "required"
opensearch_security.multifactor.totp.issuer: "Wazuh"
Configure TLS for agent communication:
<!-- /var/ossec/etc/ossec.conf (manager) -->
<remote>
<connection>secure</connection>
<port>1516</port>
<protocol>tcp</protocol>
<ssl_verify_host>yes</ssl_verify_host>
<ssl_manager_cert>/var/ossec/etc/ssl/manager.crt</ssl_manager_cert>
<ssl_manager_key>/var/ossec/etc/ssl/manager.key</ssl_manager_key>
<ssl_ca_cert>/var/ossec/etc/ssl/ca.crt</ssl_ca_cert>
</remote>
Agent TLS configuration:
<!-- /var/ossec/etc/ossec.conf (agent) -->
<client>
<server>
<address>10.0.1.100</address>
<port>1516</port>
<protocol>tcp</protocol>
</server>
<crypto_method>aes</crypto_method>
</client>
<ssl_config>
<verify_host>yes</verify_host>
<agent_cert>/var/ossec/etc/ssl/agent.crt</agent_cert>
<agent_key>/var/ossec/etc/ssl/agent.key</agent_key>
<ca_cert>/var/ossec/etc/ssl/ca.crt</ca_cert>
</ssl_config>
Configure HTTPS for Wazuh Dashboard:
# /etc/nginx/conf.d/wazuh.conf
server {
listen 443 ssl http2;
server_name wazuh.company.com;
ssl_certificate /etc/nginx/certs/wazuh.crt;
ssl_certificate_key /etc/nginx/certs/wazuh.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
proxy_pass https://localhost:443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Configure TLS for Wazuh Indexer:
# /etc/wazuh-indexer/opensearch.yml
plugins.security.ssl.http.enabled: true
plugins.security.ssl.transport.enabled: true
plugins.security.ssl.http.pemcert_filepath: /etc/wazuh-indexer/certs/node.crt
plugins.security.ssl.http.pemkey_filepath: /etc/wazuh-indexer/certs/node.key
plugins.security.ssl.http.pemtrustedcas_filepath: /etc/wazuh-indexer/certs/ca.crt
plugins.security.ssl.transport.pemcert_filepath: /etc/wazuh-indexer/certs/node.crt
plugins.security.ssl.transport.pemkey_filepath: /etc/wazuh-indexer/certs/node.key
plugins.security.ssl.transport.pemtrustedcas_filepath: /etc/wazuh-indexer/certs/ca.crt
Secure Wazuh API access:
| Endpoint | Risk Level | Access Control |
|---|---|---|
GET /agents |
Low | Analyst role |
GET /alerts |
Low | Analyst role |
GET /manager/logs |
Medium | Admin role |
PUT /agents/restart |
High | Admin role |
DELETE /agents |
High | Admin role |
POST /active-response |
Critical | Admin role |
PUT /security/users |
Critical | Administrator only |
Implement API rate limiting:
# Nginx rate limiting for Wazuh API
limit_req_zone $binary_remote_addr zone=wazuh_api:10m rate=30r/s;
location / {
limit_req zone=wazuh_api burst=50 nodelay;
proxy_pass https://localhost:55000;
}
Configure index patterns and dashboards:
# /etc/wazuh-dashboard/opensearch_dashboards.yml
opensearch_security.readonly_mode.roles: ["readonly"]
# Disable anonymous access
opensearch_security.auth.anonymous_auth_enabled: false
Restrict active response execution:
<!-- /var/ossec/etc/ossec.conf -->
<active-response>
<disabled>no</disabled>
<ca_store>/var/ossec/etc/wpkroot.pem</ca_store>
<ca_verification>yes</ca_verification>
</active-response>
<!-- Restrict commands -->
<command>
<name>firewall-drop</name>
<executable>firewall-drop.sh</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
Secure FIM configuration:
<!-- /var/ossec/etc/ossec.conf -->
<syscheck>
<disabled>no</disabled>
<frequency>43200</frequency>
<directories check_all="yes" realtime="yes">/etc,/usr/bin,/usr/sbin</directories>
<directories check_all="yes">/bin,/sbin</directories>
<!-- Skip noisy directories -->
<skip>/dev,/proc,/sys</skip>
<!-- Encryption for FIM database -->
<database>disk</database>
</syscheck>
Configure Indexer security:
# /etc/wazuh-indexer/opensearch.yml
plugins.security.enabled: true
plugins.security.allow_unsafe_democertificates: false
plugins.security.authcz.admin_dn:
- "CN=admin,OU=Wazuh,O=Wazuh,L=California,C=US"
plugins.security.nodes_dn:
- "CN=node-1,OU=Wazuh,O=Wazuh,L=California,C=US"
plugins.security.restapi.roles_enabled:
- "all_access"
- "security_rest_api_access"
Enable Indexer encryption at rest:
# /etc/wazuh-indexer/opensearch.yml
opendistro_security.encryption.backend: "local"
plugins.security.encryption.enabled: true
Configure filesystem encryption:
# Use encrypted volumes for data
# /etc/wazuh-indexer/opensearch.yml
path.data: /encrypted/wazuh-indexer/data
path.logs: /encrypted/wazuh-indexer/logs
Secure sensitive configuration:
# Use Wazuh API keystore
/var/ossec/bin/wazuh-keystore -f api -k token -v ${API_TOKEN}
# Or use environment variables
export WAZUH_API_TOKEN="${API_TOKEN}"
For Kubernetes:
apiVersion: v1
kind: Secret
metadata:
name: wazuh-secrets
type: Opaque
data:
admin-password: <base64-encoded>
api-token: <base64-encoded>
indexer-password: <base64-encoded>
Configure index lifecycle management:
# Create ILM policy via API
curl -k -X PUT "https://localhost:9200/_ilm/policy/wazuh-policy" \
-H "Content-Type: application/json" \
-u admin:${PASSWORD} \
-d '{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50gb",
"max_age": "7d"
}
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 }
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}'
Enable audit logging:
<!-- /var/ossec/etc/ossec.conf -->
<logging>
<log_format>plain</log_format>
<log_level>info</log_level>
<path>/var/ossec/logs/ossec.log</path>
</logging>
<logging>
<log_format>json</log_format>
<log_level>debug</log_level>
<path>/var/ossec/logs/ossec.json</path>
</logging>
Configure API audit logging:
# /var/ossec/api/configuration/api.yaml
access:
max_login_attempts: 5
block_time: 300
logging:
level: "info"
path: "/var/ossec/logs/api.log"
Create Wazuh alerts for security events:
<!-- /var/ossec/etc/rules/local_rules.xml -->
<group name="wazuh,">
<rule id="100100" level="12">
<if_sid>100001</if_sid>
<field name="win.system.severityValue">^ERROR$</field>
<description>Wazuh API authentication failure</description>
</rule>
<rule id="100101" level="15">
<if_sid>100001</if_sid>
<field name="data.action">user_created</field>
<description>Wazuh user creation detected</description>
</rule>
</group>
Forward Wazuh alerts to external SIEM:
<!-- /var/ossec/etc/ossec.conf -->
<syslog_output>
<server>siem.company.com</server>
<port>514</port>
<format>cef</format>
</syslog_output>