Environment: RHEL 8/9 · Wazuh Manager + Agent architecture · firewalld (iptables not in use)
After configuring firewall-drop Active Response for SSH brute force mitigation, the AR appeared to do nothing. Configuration sync was verified — the XML block existed on the agent. No errors appeared in ossec.log. active-responses.log was completely silent. The agent was running. Everything looked correct, and nothing worked.
It turned out there were four stacked failure layers, each one hiding the next. This post documents every layer, how to confirm it, and how to fix it — so you can stop at whichever layer is your actual problem.
Active response silently does nothing. No log entries anywhere.
The CauseWazuh ships two different scripts for firewall blocking:
On RHEL 8/9 with firewalld, the default ossec.conf command block may reference the wrong one:
<!-- Wrong — iptables based --> <command> <name>firewall-drop</name> <executable>firewall-drop</executable> <timeout_allowed>yes</timeout_allowed> </command> The Fix <!-- Correct — firewalld based --> <command> <name>firewall-drop</name> <executable>firewalld-drop.sh</executable> <timeout_allowed>yes</timeout_allowed> </command> How to Confirm grep -A 3 "firewall-drop" /var/ossec/etc/ossec.confConfig syncs correctly to the agent. The AR block is visible on the agent at /var/ossec/etc/shared/agent.conf. Still nothing fires.
The CauseThis is the most confusing part of the Wazuh AR architecture:
If your ossec.conf on the Manager has no <active-response> block for your rules, or has the wrong rule IDs, the Manager will never send the dispatch — regardless of what agent.conf says.
The FixThe <active-response> block must exist in /var/ossec/etc/ossec.conf on the Manager:
<active-response> <disabled>no</disabled> <command>firewall-drop</command> <location>local</location> <rules_id>5710,5712,5760,2502</rules_id> <timeout>600</timeout> </active-response> How to Confirm grep -A 8 "<active-response>" /var/ossec/etc/ossec.confThe AR block exists in ossec.conf with the correct command, but still no dispatch.
The CauseThe Owl's instinct: "Are you sure those Rule IDs are the ones being hit?"
On RHEL 8/9, SSH brute force against existing users (like root) fires different rules than brute force against non-existent users:
If your attackers are targeting root, you'll see 5760 in your alerts — not 5710 or 5712. A config block listing only 5710,5712 will sit there and do nothing.
How to ConfirmCheck what rules are actually firing during an attack:
tail -f /var/ossec/logs/alerts/alerts.log | grep "Rule:" The FixAdd the rules you're actually seeing:
<rules_id>5710,5712,5760,2502</rules_id>With analysisd.debug=2 enabled on the Manager you can see:
DEBUG: Send AR to execd: firewall-drop600With execd.debug=2 on the agent you can see the command being executed. But nothing happens. active-responses.log stays empty or shows:
/var/ossec/active-response/bin/firewall-drop: Invalid input format The CauseThis is the most dangerous failure mode — it is completely silent without debug logging.
Modern Wazuh (4.x+) passes alert data to AR scripts as a JSON blob via stdin:
firewalld-drop.sh {"version":1,"origin":{"name":"node01","module":"wazuh-execd"},"command":"add","parameters":{..."data":{"srcip":"1.2.3.4"...}}}Older or custom scripts expect positional arguments:
ACTION=$1 # expects "add" IP=$3 # expects the IP — but $3 is emptyThe script runs, gets an empty IP, passes nothing to firewall-cmd, and exits silently.
How to ConfirmEnable debug on the agent and watch what execd actually sends:
echo "execd.debug=2" >> /var/ossec/etc/local_internal_options.conf systemctl restart wazuh-agent tail -f /var/ossec/logs/ossec.log | grep -i "executing command"You'll see the full JSON payload being passed as $1.
The FixRewrite the script to read JSON from stdin:
#!/bin/bash # Wazuh active response script for firewalld # Compatible with Wazuh 4.x JSON input format # Includes: timeout support, startup flush, logging read -r INPUT_JSON COMMAND=$(echo "$INPUT_JSON" | grep -o '"command":"[^"]*"' | sed 's/"command":"//;s/"//') SRC_IP=$(echo "$INPUT_JSON" | grep -o '"srcip":"[^"]*"' | head -1 | sed 's/"srcip":"//;s/"//') LOG=/var/ossec/logs/active-responses.log LOCKFILE=/var/ossec/tmp/firewalld-drop.lock # Flush all old reject rules on first execution after agent restart # /var/ossec/tmp/ is cleared on agent restart, so lockfile auto-rearms if [ ! -f "$LOCKFILE" ]; then echo "$(date '+%Y/%m/%d %H:%M:%S') firewalld-drop: First run after startup - flushing old reject rules" >> "$LOG" firewall-cmd --list-rich-rules | grep "reject" | while read -r RULE; do firewall-cmd --permanent --remove-rich-rule="$RULE" echo "$(date '+%Y/%m/%d %H:%M:%S') firewalld-drop: Flushed: $RULE" >> "$LOG" done firewall-cmd --reload echo "$(date '+%Y/%m/%d %H:%M:%S') firewalld-drop: Flush complete" >> "$LOG" touch "$LOCKFILE" fi if [ -z "$SRC_IP" ]; then echo "$(date '+%Y/%m/%d %H:%M:%S') firewalld-drop: ERROR - No source IP in message" >> "$LOG" exit 1 fi if [ "$COMMAND" = "add" ]; then firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='$SRC_IP' reject" firewall-cmd --reload echo "$(date '+%Y/%m/%d %H:%M:%S') firewalld-drop: Blocked $SRC_IP" >> "$LOG" # Return check_keys response so execd registers the timeout # Without this, execd never adds the block to its timeout list # and the <timeout> value in ossec.conf has no effect echo '{"version":1,"origin":{"name":"firewalld-drop","module":"active-response"},"command":"check_keys","parameters":{"keys":["'"$SRC_IP"'"]}}' elif [ "$COMMAND" = "delete" ]; then firewall-cmd --permanent --remove-rich-rule="rule family='ipv4' source address='$SRC_IP' reject" RESULT=$? firewall-cmd --reload echo "$(date '+%Y/%m/%d %H:%M:%S') firewalld-drop: Unblocked $SRC_IP (exit code: $RESULT)" >> "$LOG" fiDeploy to all agents:
chmod 750 /var/ossec/active-response/bin/firewalld-drop.sh chown root:wazuh /var/ossec/active-response/bin/firewalld-drop.shNote on the check_keys response: This is critical for timeout to work. Without it, execd logs:
Active response won't be added to timeout list. Message not received with alert keys from script...and the <timeout> value in your config is completely ignored. Blocks become permanent.
Manager — analysisd:
echo "analysisd.debug=2" >> /var/ossec/etc/local_internal_options.conf systemctl restart wazuh-manager tail -f /var/ossec/logs/ossec.log | grep -i "active\|sending\|dispatch"Agent — execd:
echo "execd.debug=2" >> /var/ossec/etc/local_internal_options.conf systemctl restart wazuh-agent tail -f /var/ossec/logs/ossec.log | grep -i "active\|execd\|firewall"Clean up when done:
sed -i '/analysisd.debug=2/d' /var/ossec/etc/local_internal_options.conf sed -i '/execd.debug=2/d' /var/ossec/etc/local_internal_options.conf systemctl restart wazuh-manager # or wazuh-agent on the agentTested on: RHEL 9 · Wazuh 4.x · firewalld · Agent-Manager architecture