Wazuh is an open-source security platform that combines SIEM and endpoint detection in one stack: it collects logs and events from your servers, runs them against detection rules, and raises alerts. This walkthrough deploys Wazuh server (all-in-one) on one machine, enrolls an agent on a second machine, writes a real custom detection rule with a decoder, and wires up email alerting and automatic IP blocking. By the end you will have a working host intrusion detection system catching brute-force attempts.
Tested on Ubuntu 22.04 and 24.04. Commands are for a Debian/Ubuntu target.
Prerequisites
- Two Linux hosts: one for the Wazuh server, one to protect as an agent. A single host works for testing but you will not see agent enrollment.
- The server needs at least 4 GB RAM (8 GB recommended). The bundled indexer is Java-based and will be killed by the OOM reaper on a 2 GB box.
- Root or sudo on both hosts.
- Network reachability from the agent to the server on TCP 1514 and 1515.
Version note: I am using the 4.9 release paths below. Check packages.wazuh.com for the current 4.x release number before you run this and swap the version in the URLs if it has moved on.
Step 1: install the Wazuh server (all-in-one)
Wazuh ships an installation assistant that deploys the manager, indexer, and dashboard together. On the server host:
curl -sO https://packages.wazuh.com/4.9/wazuh-install.sh
sudo bash ./wazuh-install.sh -aThe -a flag means all-in-one. This takes several minutes. When it finishes it prints the dashboard URL and the admin password. Pull the generated credentials back out at any time with:
sudo tar -O -xf wazuh-install-files.tar wazuh-install-files/wazuh-passwords.txtOpen https://<server-ip> in a browser, accept the self-signed certificate, and log in as admin with that password. You should see the Wazuh dashboard with zero agents connected.
Step 2: enroll an agent
On the host you want to protect, add the Wazuh repository and install the agent, pointing it at the server IP:
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | \
sudo gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import
sudo 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" | \
sudo tee /etc/apt/sources.list.d/wazuh.list
sudo apt-get update
sudo WAZUH_MANAGER="<server-ip>" WAZUH_AGENT_NAME="agent01" apt-get install -y wazuh-agentStart it and enable it at boot:
sudo systemctl daemon-reload
sudo systemctl enable --now wazuh-agentBack on the server, confirm the agent registered and is active:
sudo /var/ossec/bin/agent_control -lYou should see agent01 with status Active. It will also appear in the dashboard under Agents.
Step 3: turn on file integrity monitoring
File integrity monitoring (FIM) alerts when sensitive files change. On the agent, edit /var/ossec/etc/ossec.conf and set the syscheck block to watch system directories in real time:
<syscheck>
<disabled>no</disabled>
<frequency>43200</frequency>
<directories check_all="yes" realtime="yes">/etc,/usr/bin,/usr/sbin,/bin,/sbin</directories>
<directories check_all="yes" realtime="yes">/var/www</directories>
</syscheck>Restart the agent:
sudo systemctl restart wazuh-agentNow change a watched file on the agent to generate an event:
sudo touch /etc/test-fim-fileWithin a few seconds you will see a FIM alert in the dashboard (default rule 554, “File added to the system”).
Step 4: write a custom detection rule with a decoder
This is where Wazuh earns its keep. Say you run an application that writes auth failures to /var/log/myapp/access.log in this format:
2026-07-16 10:00:00 WARN auth failed for user admin from 203.0.113.5First, tell the agent to collect that log. Add this to the agent’s ossec.conf and restart the agent:
<localfile>
<log_format>syslog</log_format>
<location>/var/log/myapp/access.log</location>
</localfile>Next, on the server, teach Wazuh how to parse the line. Add a decoder to /var/ossec/etc/decoders/local_decoder.xml:
<decoder name="myapp">
<prematch>auth failed for user</prematch>
</decoder>
<decoder name="myapp-fields">
<parent>myapp</parent>
<regex>auth failed for user (\S+) from (\S+)</regex>
<order>user, srcip</order>
</decoder>Then add the rules to /var/ossec/etc/rules/local_rules.xml. The first rule fires on a single failure; the second correlates five failures from the same IP inside two minutes into one high-severity brute-force alert:
<group name="myapp,authentication_failed,">
<rule id="100100" level="5">
<decoded_as>myapp</decoded_as>
<description>MyApp: auth failed for user $(user) from $(srcip)</description>
</rule>
<rule id="100101" level="10" frequency="5" timeframe="120">
<if_matched_sid>100100</if_matched_sid>
<same_source_ip />
<description>MyApp: possible brute force, 5 failed logins from $(srcip) in 120s</description>
<group>brute_force,</group>
</rule>
</group>Custom rule IDs must be 100000 or higher. Anything below that collides with Wazuh’s built-in rule set and will be ignored or misbehave.
Step 5: test the rule before you restart anything
Never restart the manager on an unverified rule. Use the built-in log tester instead:
sudo /var/ossec/bin/wazuh-logtestPaste in a sample line:
2026-07-16 10:00:00 WARN auth failed for user admin from 203.0.113.5You should see it decoded with user: admin, srcip: 203.0.113.5, and matched to rule 100100. Once that works, apply it:
sudo systemctl restart wazuh-managerStep 6: alert by email and block the attacker automatically
To email on high-severity alerts, edit the server ossec.conf. In the <global> block:
<global>
<email_notification>yes</email_notification>
<smtp_server>localhost</smtp_server>
<email_from>[email protected]</email_from>
<email_to>[email protected]</email_to>
</global>
<alerts>
<email_alert_level>10</email_alert_level>
</alerts>To also drop the source IP at the firewall for ten minutes when the brute-force rule fires, add an active response tied to rule 100101:
<active-response>
<command>firewall-drop</command>
<location>local</location>
<rules_id>100101</rules_id>
<timeout>600</timeout>
</active-response>Restart the manager once more to apply. Watch alerts arrive in real time:
sudo tail -f /var/ossec/logs/alerts/alerts.logNow generate five failed logins from one IP against your app (or five bad SSH passwords if you point the same pattern at /var/log/auth.log), and you will see rule 100101 fire, an email go out, and the IP dropped.
What goes wrong
Agent stuck on “Never connected”. The server firewall is blocking enrollment. Open TCP 1514 (events) and 1515 (enrollment) from the agent to the server.
Indexer killed after install. Not enough RAM. Give the server 8 GB, or set the indexer JVM heap to no more than half of RAM in the indexer’s jvm.options.
Custom rule never fires. Three usual causes: you used a rule ID below 100000, you forgot to restart the manager after editing rules, or the decoder did not match. Always confirm in wazuh-logtest first.
Correlation rules misbehave. The frequency and timeframe counters depend on accurate clocks. If the agent and server clocks drift, correlation and TLS both break. Run NTP on both hosts.
Certificate errors on enrollment. The install assistant generates certificates for the hostname or IP you gave it. If you change the server address later, regenerate them rather than editing by hand.