Introduction
A proper Security Information and Event Management (SIEM) system is no longer optional—it’s a requirement for compliance and a necessity for detecting threats. After evaluating several solutions, I chose Wazuh as our primary SIEM. It’s open source, incredibly capable, and integrates well with existing infrastructure.
This guide covers deploying Wazuh in production, creating detection rules, and building effective security workflows.
Why Wazuh?
| Capability | Wazuh | Splunk | ELK Stack |
|---|---|---|---|
| License Cost | Free | $$$$ | Free (basic) |
| Agent-based Collection | Yes | Yes | Requires Beats |
| File Integrity Monitoring | Built-in | Add-on | Add-on |
| Vulnerability Detection | Built-in | Add-on | No |
| Compliance Reporting | Built-in | Add-on | No |
| Learning Curve | Moderate | High | High |
Wazuh combines HIDS, log analysis, vulnerability detection, and compliance monitoring in one platform—impressive for a free tool.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Wazuh Dashboard │
│ (Kibana + Wazuh Plugin) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ Wazuh Indexer │
│ (OpenSearch Cluster) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ Wazuh Manager │
│ (Rules Engine, API, Agent Management) │
└─────────────────────────┬───────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐
│ Agent │ │ Agent │ │ Syslog │
│ (Linux) │ │ (Windows) │ │ Sources │
└─────────┘ └───────────┘ └─────────┘
Installation
Single-Node Deployment
For environments up to 100 agents:
# Download and run the installation assistant
curl -sO https://packages.wazuh.com/4.7/wazuh-install.sh
chmod +x wazuh-install.sh
./wazuh-install.sh -a
Distributed Deployment
For larger environments, deploy components separately:
# Node 1: Wazuh Indexer
./wazuh-install.sh --wazuh-indexer node-1
# Node 2: Wazuh Manager
./wazuh-install.sh --wazuh-server wazuh-1
# Node 3: Wazuh Dashboard
./wazuh-install.sh --wazuh-dashboard dashboard
Initial Configuration
# /var/ossec/etc/ossec.conf (key sections)
<ossec_config>
<global>
<email_notification>yes</email_notification>
<smtp_server>smtp.yourorg.com</smtp_server>
<email_from>wazuh@yourorg.com</email_from>
<email_to>security@yourorg.com</email_to>
<email_maxperhour>12</email_maxperhour>
</global>
<alerts>
<log_alert_level>3</log_alert_level>
<email_alert_level>10</email_alert_level>
</alerts>
<remote>
<connection>secure</connection>
<port>1514</port>
<protocol>tcp</protocol>
</remote>
</ossec_config>
Agent Deployment
Linux Agents
# Debian/Ubuntu
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --dearmor > /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" > /etc/apt/sources.list.d/wazuh.list
apt update && apt install wazuh-agent -y
# Configure agent
cat > /var/ossec/etc/ossec.conf << 'EOF'
<ossec_config>
<client>
<server>
<address>wazuh.yourorg.com</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
<enrollment>
<enabled>yes</enabled>
<manager_address>wazuh.yourorg.com</manager_address>
<agent_name>webserver-01</agent_name>
<groups>linux,webservers,production</groups>
</enrollment>
</client>
</ossec_config>
EOF
systemctl enable wazuh-agent
systemctl start wazuh-agent
Windows Agents
# Download and install
Invoke-WebRequest -Uri "https://packages.wazuh.com/4.x/windows/wazuh-agent-4.7.0-1.msi" -OutFile wazuh-agent.msi
msiexec.exe /i wazuh-agent.msi /q WAZUH_MANAGER="wazuh.yourorg.com" WAZUH_AGENT_GROUP="windows,workstations"
# Start service
NET START WazuhSvc
Automated Deployment with Ansible
# playbooks/deploy-wazuh-agent.yml
---
- name: Deploy Wazuh Agent
hosts: all
become: yes
vars:
wazuh_manager: "wazuh.yourorg.com"
tasks:
- name: Add Wazuh repository (Debian)
when: ansible_os_family == "Debian"
block:
- name: Add GPG key
apt_key:
url: https://packages.wazuh.com/key/GPG-KEY-WAZUH
state: present
- name: Add repository
apt_repository:
repo: "deb https://packages.wazuh.com/4.x/apt/ stable main"
state: present
- name: Install agent
apt:
name: wazuh-agent
state: present
update_cache: yes
- name: Configure agent
template:
src: ossec.conf.j2
dest: /var/ossec/etc/ossec.conf
notify: Restart wazuh-agent
- name: Enable and start agent
service:
name: wazuh-agent
enabled: yes
state: started
handlers:
- name: Restart wazuh-agent
service:
name: wazuh-agent
state: restarted
Detection Rules
Understanding Rule Structure
<!-- /var/ossec/etc/rules/local_rules.xml -->
<group name="custom,">
<!-- SSH brute force detection -->
<rule id="100001" level="10" frequency="5" timeframe="120">
<if_matched_sid>5716</if_matched_sid>
<same_source_ip />
<description>SSH brute force attempt from $(srcip)</description>
<group>authentication_failures,</group>
</rule>
<!-- Suspicious process execution -->
<rule id="100002" level="12">
<if_sid>530</if_sid>
<match>nc -e|nc.traditional|ncat -e|bash -i</match>
<description>Possible reverse shell detected</description>
<group>attack,shell,</group>
</rule>
<!-- Privilege escalation attempt -->
<rule id="100003" level="14">
<if_sid>5402</if_sid>
<user>^(?!root).*</user>
<match>sudo su -|sudo -i|sudo bash</match>
<description>User $(user) attempted privilege escalation</description>
<group>privilege_escalation,</group>
</rule>
</group>
File Integrity Monitoring
<!-- /var/ossec/etc/ossec.conf -->
<syscheck>
<frequency>43200</frequency>
<scan_on_start>yes</scan_on_start>
<!-- Critical system directories -->
<directories check_all="yes" realtime="yes">/etc,/usr/bin,/usr/sbin</directories>
<directories check_all="yes" realtime="yes">/bin,/sbin</directories>
<!-- Web application files -->
<directories check_all="yes" realtime="yes" report_changes="yes">/var/www</directories>
<!-- Configuration files -->
<directories check_all="yes" realtime="yes">/etc/ssh</directories>
<directories check_all="yes" realtime="yes">/etc/pam.d</directories>
<!-- Ignore noisy directories -->
<ignore>/etc/mtab</ignore>
<ignore>/etc/resolv.conf</ignore>
<ignore type="sregex">.log$|.swp$</ignore>
</syscheck>
Custom Decoders
<!-- /var/ossec/etc/decoders/local_decoder.xml -->
<!-- Custom application log decoder -->
<decoder name="custom-app">
<program_name>myapp</program_name>
</decoder>
<decoder name="custom-app-auth">
<parent>custom-app</parent>
<regex>Authentication (failed|success) for user (\S+) from (\S+)</regex>
<order>status, user, srcip</order>
</decoder>
<decoder name="custom-app-error">
<parent>custom-app</parent>
<regex>ERROR: (\S+) - (.+)</regex>
<order>error_code, error_message</order>
</decoder>
Vulnerability Detection
Configure Vulnerability Scanner
<!-- /var/ossec/etc/ossec.conf -->
<vulnerability-detector>
<enabled>yes</enabled>
<interval>5m</interval>
<min_full_scan_interval>6h</min_full_scan_interval>
<run_on_start>yes</run_on_start>
<!-- Operating system CVE feeds -->
<provider name="canonical">
<enabled>yes</enabled>
<os>focal</os>
<os>jammy</os>
<update_interval>1h</update_interval>
</provider>
<provider name="redhat">
<enabled>yes</enabled>
<update_interval>1h</update_interval>
</provider>
<provider name="nvd">
<enabled>yes</enabled>
<update_interval>1h</update_interval>
</provider>
</vulnerability-detector>
Vulnerability Reporting
# Query vulnerabilities via API
curl -k -u admin:admin -X GET "https://localhost:55000/vulnerability/agent/001" | jq '.data.affected_items[] | {cve: .cve, severity: .severity, package: .package.name}'
Compliance Monitoring
PCI-DSS Configuration
<!-- Enable PCI-DSS checks -->
<wodle name="sca">
<enabled>yes</enabled>
<scan_on_start>yes</scan_on_start>
<interval>12h</interval>
<policies>
<policy>cis_ubuntu22-04.yml</policy>
<policy>pci_dss_v3.2.1.yml</policy>
</policies>
</wodle>
Custom Compliance Policy
# /var/ossec/etc/shared/custom_policy.yml
policy:
id: "custom_security_policy"
file: "custom_security_policy.yml"
name: "Custom Security Baseline"
description: "Organization-specific security requirements"
requirements:
title: "SSH Configuration"
description: "Verify SSH is properly configured"
checks:
- id: 10001
title: "SSH root login disabled"
condition: all
rules:
- "f:/etc/ssh/sshd_config -> r:^PermitRootLogin no$"
- id: 10002
title: "SSH protocol version 2"
condition: all
rules:
- "f:/etc/ssh/sshd_config -> r:^Protocol 2$"
- id: 10003
title: "Password authentication disabled"
condition: all
rules:
- "f:/etc/ssh/sshd_config -> r:^PasswordAuthentication no$"
Active Response
Automatic IP Blocking
<!-- /var/ossec/etc/ossec.conf -->
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100001</rules_id> <!-- SSH brute force -->
<timeout>3600</timeout>
</active-response>
<active-response>
<command>firewall-drop</command>
<location>local</location>
<level>12</level> <!-- High severity events -->
<timeout>7200</timeout>
</active-response>
Custom Response Script
#!/bin/bash
# /var/ossec/active-response/bin/slack-alert.sh
ALERT_FILE=$1
SRCIP=$(cat $ALERT_FILE | grep srcip | cut -d: -f2)
RULE=$(cat $ALERT_FILE | grep rule | cut -d: -f2)
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"🚨 Security Alert: Rule $RULE triggered from $SRCIP\"}" \
https://hooks.slack.com/services/YOUR/WEBHOOK
<!-- Register custom command -->
<command>
<name>slack-alert</name>
<executable>slack-alert.sh</executable>
<timeout_allowed>no</timeout_allowed>
</command>
Integration with External Tools
TheHive Integration
#!/usr/bin/env python3
# /var/ossec/integrations/thehive.py
import json
import requests
import sys
THEHIVE_URL = "https://thehive.yourorg.com"
THEHIVE_KEY = "your-api-key"
def create_alert(alert_data):
headers = {
"Authorization": f"Bearer {THEHIVE_KEY}",
"Content-Type": "application/json"
}
alert = {
"title": f"Wazuh: {alert_data['rule']['description']}",
"description": json.dumps(alert_data, indent=2),
"severity": map_severity(alert_data['rule']['level']),
"source": "wazuh",
"sourceRef": alert_data['id'],
"type": "wazuh-alert"
}
response = requests.post(
f"{THEHIVE_URL}/api/alert",
headers=headers,
json=alert
)
return response.status_code
def map_severity(level):
if level >= 12:
return 3 # High
elif level >= 8:
return 2 # Medium
return 1 # Low
if __name__ == "__main__":
alert_data = json.load(sys.stdin)
create_alert(alert_data)
Lessons Learned
- Start with defaults, then tune. Wazuh’s default rules are good—don’t disable them before understanding what they catch.
- Agent groups simplify management. Group agents by OS, role, and environment for targeted configuration.
- File integrity monitoring needs baselines. Run FIM after changes, not during, or you’ll drown in alerts.
- Active response requires caution. Auto-blocking can cause outages if rules are too aggressive.
- Integrate early. Connect Wazuh to your incident response workflow from day one.
Conclusion
Wazuh provides enterprise-grade security monitoring without the enterprise price tag. The initial setup requires investment, but the result is comprehensive visibility into your security posture.
Start with basic host monitoring, add custom rules as you understand your environment, and gradually enable active response. Security is a journey, not a destination.