Active response is the part of Wazuh that acts on a detection: blocks an address, disables an account, deletes a file. In Wazuh 5.0 the trigger path is inverted compared with 4.x. An Alerting monitor on the indexer writes an execution request to the wazuh-active-responses data stream, the manager reads it and dispatches to the agent, and the agent runs the script. Nothing fires until you build both halves. That default is right, and this lesson argues for keeping it until a specific detection has earned the exception.
What you need
- A Wazuh 5.0 deployment producing findings you trust, and dashboard admin access.
- One disposable endpoint. Not a production host, not a domain controller, not the box you are reading this on.
- The notifications lesson, because active response is built on the same Alerting module.
- The companion files: a custom script skeleton with a dry-run mode, and the gating checklist as a file you can tick off.
How the model works
Five steps, and the first two live on the indexer:
- An Alerting monitor of type Active response evaluates findings against a trigger condition on a schedule.
- When the trigger fires, the monitor writes an execution request to the
wazuh-active-responsesdata stream. - The Wazuh manager reads the request and sends the command, with the relevant finding data, to the selected endpoint.
- The agent runs the named executable, passing it a JSON message on standard input.
- Results are written back to
wazuh-active-responses*and are visible under Security operations > Incident response > Responses.
The monitor type has hard constraints, which the documentation states explicitly:
| Requirement | Value |
|---|---|
| Target indices | Must match wazuh-findings-v5-* |
| Schedule | Maximum execution interval of 1 minute |
| Trigger type | DocumentLevelTrigger only |
Retention is short, and this part is verified rather than read. On a 5.0.0-beta5 install, wazuh-active-responses is one of the data streams the Setup plugin creates before a single response is configured, and the ISM policy that manages it is exactly as documented:
"policy_id" : "stream-active-responses-policy",
"min_doc_count" : 200000000,
"min_index_age" : "3d"
Three days, editable under Indexer Management > Index Management > State management policies. If you want an audit trail of automated actions that outlives a long weekend, extend the policy or ship the records somewhere else. Retention is covered in upgrade, backup and retention.
Execution logs land on the endpoint:
| Platform | Log |
|---|---|
| Linux | /var/ossec/logs/active-responses.log |
| macOS | /Library/Ossec/logs/active-responses.log |
| Windows | C:\Program Files (x86)\ossec-agent\active-response\active-responses.log |
Stateless and stateful
A stateless response acts once and does not revert. Deleting a file is stateless. A stateful response acts, then reverts automatically when the configured timeout expires. Temporarily blocking an address is stateful. The type and, for stateful, the timeout are both set in the active response configuration and arrive in the message as wazuh.active_response.type and wazuh.active_response.stateful_timeout.
Prefer stateful with a short timeout. A block that expires on its own turns a bad decision into a nuisance rather than an outage.
Location
Local runs the response on the agent that produced the finding. Defined agent runs it on a named agent regardless of where the event occurred, which is how you drive a firewall or a proxy from a detection elsewhere. All runs it on every agent.
The built-in scripts
Wazuh 5.0 consolidated the 4.x firewall scripts into one block-ip executable per platform, which tries the available mechanisms in order and moves on when one is unsupported.
| Platform | block-ip order |
Also available |
|---|---|---|
| Linux | firewalld, iptables or ip6tables, /etc/hosts.deny, null route |
disable-account (passwd -l / passwd -u) |
| FreeBSD | ipfw, pf, /etc/hosts.deny, null route |
|
| OpenBSD | pf, /etc/hosts.deny, null route |
|
| NetBSD | npf, /etc/hosts.deny, null route |
|
| macOS | pf, /etc/hosts.deny |
|
| Windows | netsh advfirewall, then a blackhole route |
That fallback chain is convenient and slightly alarming. On a host where firewalld is absent and iptables belongs to something else, you can end up with a /etc/hosts.deny entry or a null route that your configuration management neither knows about nor removes. Know which mechanism your hosts will land on before enabling block-ip.
Configure one, end to end
Two halves. First the response, under Explore > Active Responses > Create active response: a name, the executable, optional extra arguments, the location, the type, and for stateful the timeout in seconds. No manager restart is needed; the manager distributes the configuration to the agents that need it.
Second the monitor, under Explore > Alerting > Create Monitor, with type Active response, an interval of 1 minute, one findings index, and a query pinned to a specific rule. The documented example queries wazuh-findings-v5-system-activity for a single wazuh.rule.id, adds a trigger, and attaches the response through Add active response in the trigger’s Actions section.
Pin the query to a rule ID. A monitor keyed on severity alone will fire for every future rule that inherits that severity, including rules you have never seen, added by a content update you did not review.
Write a custom script
Custom scripts can be in any language the endpoint supports. The contract is a JSON message on standard input:
{
"wazuh": {
"active_response": {
"name": "",
"executable": "",
"location": "",
"agent_id": "",
"type": "",
"stateful_timeout": 0
},
"agent": { "id": "", "name": "" }
},
"source": {},
"user": {},
"command": ""
}
command is enable to act and disable to revert. Finding data arrives as top-level Wazuh Common Schema fields, so a script that blocks an address reads source.ip and a script that deletes a file reads file.path.
A stateless script reads standard input, parses the JSON, confirms command is enable, and acts. A stateful script must handle disable as well, and must produce a key identifying what it acted on so the revert can find it again.
Install on Linux:
sudo cp custom-ar.py /var/ossec/active-response/bin/custom-ar
sudo chmod 750 /var/ossec/active-response/bin/custom-ar
sudo chown root:wazuh /var/ossec/active-response/bin/custom-ar
macOS uses /Library/Ossec/active-response/bin, Windows uses C:\Program Files (x86)\ossec-agent\active-response\bin. Windows agents execute binaries, so convert the script with PyInstaller or call it from a batch launcher, both of which the documentation describes.
The skeleton in the companion files for this lesson follows this contract and adds two things the documented sample does not have: a DRY_RUN flag that logs the intended action instead of performing it, and a never-touch list checked before anything happens. Neither is optional in my view.
Now the argument: leave it off
Everything above is how. Here is when, and the answer is usually not yet.
Active response is the only part of Wazuh that changes the state of the thing it watches. Every other module fails as noise or a blind spot. This one fails as an outage you caused, during an incident, with an automation nobody on the bridge call understands.
My position, unchanged across every deployment I have run: off by default, opted into per environment and per detection, after the detection has been tuned. The reason has nothing to do with distaste for automation. The prerequisite is a detection with a zero false positive rate over weeks of real traffic, and almost no detection has that on day one.
The failure modes, concretely
Blocking a shared egress address. One finding sourced from a NAT gateway, a corporate proxy or a VPN concentrator blocks everyone behind it. This is the most common real incident, and it is worst in environments where a single address represents a whole office or a whole customer.
Blocking yourself. The address you administer from is in the same range as the noisy one. Now the console is unreachable and the revert path runs through the thing you just blocked.
Blocking the scanner. Your vulnerability scanner trips a detection, gets blocked on a few hundred hosts, and coverage quietly degrades. Nobody notices for a month because the scanner still reports, it just reports less.
Acting on a spoofed source. For any detection where the source address is attacker-controlled and unvalidated, automated blocking is a denial-of-service primitive handed to the attacker. They forge traffic from your payment gateway and you block your payment gateway.
Acting on a domain controller. A response scoped All, or a group that quietly grew to include tier-0, and authentication degrades estate-wide.
The revert that never runs. A stateful block whose disable branch was never tested, or whose agent restarted mid-timeout. The block becomes permanent and undocumented, and surfaces six weeks later as an unexplained connectivity problem.
Re-firing. Findings can re-fire. An action that is not idempotent applied fifty times in an hour produces fifty firewall rules, or fifty attempts to delete a file that is already gone.
The gating checklist
The full version ships as a file in the companion files for this lesson. The short version, per environment:
The detection. The rule has produced findings here for at least 14 days. Every one has been reviewed. The false positive rate is zero, not low. The monitor query pins wazuh.rule.id, not severity. The rule is under change control so it cannot be silently widened.
The blast radius. One findings data stream, not a wildcard. Location Local or Defined agent. A never-touch list covering domain controllers, jump hosts, load balancers, VPN and NAT egress, scanner sources, monitoring sources and your own admin ranges. The response cannot sever the path used to recover the endpoint. Someone has answered out loud: what happens if this fires on 500 agents in one minute?
The script. Idempotent. Stateful or stateless chosen deliberately, with a timeout you can live with. The revert path tested on its own. Non-zero exit reserved for genuine failure, so a refusal does not look like an error. Run in dry-run mode against real findings first, and read the log lines.
The operations. A documented manual reversal that works without Wazuh. On-call knows the response exists and how to kill it. Killing it is one step. Executions are reviewed. The change has an owner and a review date.
The rollout. One non-critical group. A full business cycle including a month end and a patch window. Then widen one group at a time, repeating the review.
If you cannot tick the blast radius section, stop. The rest does not matter.
The alternative that is usually better
Send the finding to an automation platform instead, and let the response be a ticket, a page, an enrichment, and a one-click action a human approves. You keep the speed where speed is cheap and put a person in front of the irreversible part. That path is the subject of the SOAR lesson, and for most teams it is the right first automation, with active response added later for the narrow cases that have earned it.
Common errors
The monitor exists but no request is ever written
Cause: the monitor is not of type Active response, the index does not match wazuh-findings-v5-*, the interval exceeds 1 minute, or the trigger is not a document-level trigger. All four are documented constraints.
Fix: recreate the monitor with the correct type and a 1-minute interval over a single findings data stream.
The request is written but the script never runs
Cause: the executable is not present in the agent’s active response bin directory, or permissions are wrong.
Fix: confirm the file exists on the agent, with mode 750 and owner root:wazuh on Linux and macOS. On Windows, confirm you deployed an executable or a batch launcher, since the Windows agent does not run a .py file directly.
The script runs but nothing happens on the endpoint
Cause: the field the script reads is absent from the message. A script expecting source.ip gets nothing from a file integrity finding, which carries file.path.
Fix: read the raw message. The documented sample logs the entire JSON input to the active response log before parsing it, which is the fastest way to see what actually arrived.
The stateful response never reverts
Cause: the disable branch is missing, throws, or depends on state that did not survive the timeout.
Fix: test disable independently by feeding the script a message with "command": "disable" on standard input. Make the revert tolerant of an enable that partially failed.
Active response fires on an agent you did not intend
Cause: location All, or an agent that joined a group you scoped the response to.
Fix: switch to Local or Defined agent. Audit group membership, since groups are also the scoping mechanism for everything else.
repeated_offenders appears to be ignored
Cause: it was set in a shared agent.conf. That option must be in the agent’s local configuration file, cannot be distributed through centralised configuration, and is not supported on Windows agents.
Fix: set it locally through configuration management. The manager-side timeout applies to the first occurrence; the comma-separated list of up to five increasing values, in minutes, applies to subsequent ones.
Turning it off in a hurry
Two switches, both worth rehearsing before you need them.
Stop it at source: disable the Active response monitor in the Alerting module. No further execution requests are written.
Stop it at the endpoint: in that agent’s local configuration file, set
<active-response>
<disabled>yes</disabled>
</active-response>
<disabled> is mandatory in every <active-response> section, so there is no ambiguity about what the block is doing when someone reads it at three in the morning.
Official documentation for this topic: Active response: how it works, custom active response scripts and the 4.x script migration notes. The rule side is covered in writing custom detection rules, and the FIM triggers most people start from are in file integrity monitoring.
Next: Wazuh Malware Detection with VirusTotal and YARA.
Files for this lesson
Every file this lesson refers to, exactly as used. Open a file to read it in the browser, or download the set as a zip. Download wazuh-active-response.zip. The full set for all lessons is at wazuh-tutorial-files.zip.
README.md2.7 KBcustom-ar-skeleton.py5.6 KBgating-checklist.md4.0 KB
Frequently asked questions
Is Wazuh active response enabled by default?
No. Nothing fires until you create an active response configuration and an Active response monitor that references it. That default is correct and worth keeping in most environments until a specific detection has been proven false-positive free for weeks.
How does active response work in Wazuh 5.0?
An Alerting monitor of type Active response evaluates findings on the indexer. When a trigger fires, it writes an execution request to the wazuh-active-responses data stream. The manager reads that request and dispatches the command to the target agent, which runs the script. Results are written back to the same index.
What replaced firewall-drop in Wazuh 5.0?
A single block-ip executable per platform. It tries the available firewall mechanisms in a fixed order: on Linux firewalld, then iptables or ip6tables, then /etc/hosts.deny, then a null route. On Windows it tries netsh advfirewall and falls back to a blackhole route. You no longer pick a firewall-specific script.
How do I stop a Wazuh active response quickly?
Disable the Active response monitor in the Alerting module, which stops new execution requests at source. To stop an individual endpoint from acting, set active-response disabled to yes in that agent's local configuration. Have both paths rehearsed before you enable anything.
Do Wazuh 4.x active response scripts work in 5.0?
No. The command values changed from add and delete to enable and disable, the JSON structure moved from parameters.alert to top-level Wazuh Common Schema fields, and ar.conf no longer exists because the metadata now travels inside the message. Every custom script needs rewriting.