Why this matters beyond the homelab: A detection rule that fails loudly is an inconvenience. A detection rule that fails silently is worse than having no rule at all, because it produces confidence without coverage. Validating that a rule actually fires, rather than that it merely loaded, is the difference between a SIEM and a dashboard that makes you feel safe.
The Setup
I was deploying local_rules.xml v2.0 across a fifteen-agent Wazuh fleet. Forty-one rules spanning SSH and authentication, Windows and Active Directory events, privilege escalation, file integrity monitoring on the documentation pipeline, Wazuh self-monitoring, and a block of suppression rules for known-benign noise.
The manager restarted cleanly. wazuh-logtest was happy. The rules were present in the loaded ruleset.
Roughly a third of them never fired. Not “fired rarely.” Never, across days, including against events I generated specifically to trigger them.
Trap One: type="pcre2" on <field>
The suppression rules were the obvious tell. I had written a rule to suppress authentication noise from machine accounts, which in Active Directory always end in $. Straightforward regex:
<!-- silently matches nothing -->
<field name="win.eventdata.targetUserName" type="pcre2">\$$</field>
That rule never fired. Meanwhile rule 100303, written without the type attribute, fired correctly against the same event stream.
Removing the attribute fixed it immediately:
<!-- matches -->
<field name="win.eventdata.targetUserName">\$$</field>
Same field, same pattern, same events. The only change was dropping type="pcre2".
In Wazuh 4.14.2, declaring type="pcre2" on a <field> element causes the rule to silently no-op. Not error. Not warn. Not refuse to load. ossec.log says nothing at any log level. The rule is present in the loaded ruleset and simply never matches.
I found this by bisecting a working rule against a non-working one until only that attribute differed, then flipping it back and forth to confirm causation both directions. That is the only method that works when there is no error message to read.
To be clear about scope: this is what I observed on 4.14.2 specifically. I have not tested whether it behaves the same on other releases, and you should verify against your own version rather than take mine as universal. Which is itself the point of this post: test the rule, do not trust the syntax.
Trap Two: dstuser and srcuser Are Not Rule Tags
The v1.0 ruleset, which existed only in documentation and had never actually been deployed, was full of this:
<!-- not a thing -->
<dstuser>administrator</dstuser>
<srcuser>svc_backup</srcuser>
Those names appear throughout Wazuh’s decoded output and its alert JSON, so they look like they should work as rule tags. They are not rule tags. The correct static field tag is <user>:
<user>administrator</user>
Same silent-failure class. The rule loads, matches nothing.
Trap Three: timeframe Is an Attribute, Not a Child Element
Frequency-based rules need a window. The wrong way:
<rule id="100105" level="10" frequency="8">
<timeframe>120</timeframe> <!-- wrong position -->
...
</rule>
The right way, on the opening tag:
<rule id="100105" level="10" frequency="8" timeframe="120">
...
</rule>
Get this wrong and the correlation window never applies, so a rule designed to fire on eight failures in two minutes does not fire on anything.
The Pattern Connecting All Three
Wazuh’s rule parser is permissive about things it does not recognize. Unknown attributes and misplaced elements do not stop a rule from loading. That permissiveness is defensible as a design choice, since a strict parser would mean one typo takes your entire SIEM offline on restart. But it shifts the entire burden of validation onto you.
The consequence is a specific and dangerous failure mode: a ruleset that loads successfully and covers nothing. You get a clean restart, forty-one rules in the manager, a green dashboard, and no detection.
The v1.0 ruleset was exactly this. Documented as “25+ custom detection rules deployed.” Never actually deployed, and riddled with patterns that would not have worked if it had been. That gap between what documentation claimed and what the manager was running is the most uncomfortable finding of the whole exercise, and I corrected the Codex entry to reflect that v2.0 is the first real deployment.
How to Actually Validate a Rule
Test against a real event, not a hypothetical one. Capture an actual log line from the source you care about and feed it in:
/var/ossec/bin/wazuh-logtest
Paste the raw event. Confirm it reaches your rule ID, not just that it decodes.
Confirm the rule is loaded and distinguish that from firing:
grep -c "<rule id=" /var/ossec/etc/rules/local_rules.xml
/var/ossec/bin/wazuh-analysisd -t
-t verifies the ruleset parses. It does not verify your rules match anything. Those are separate claims and only the second one matters.
Then verify in the alert stream. Trigger the condition for real and confirm the alert appears:
tail -f /var/ossec/logs/alerts/alerts.json | grep '"id":"100303"'
Bisect when something silently fails. Take a rule you know works, change one thing toward the broken one, retest. Repeat until it breaks. With no error message, differential testing is the only diagnostic available.
Track fire counts per rule over time. A custom rule that has never fired in thirty days is either perfectly tuned or completely broken, and you cannot tell which by looking at the XML. Pull alert counts grouped by rule ID periodically and treat permanent zeros as suspects rather than successes.
Takeaways
A loaded rule is not a working rule. Wazuh will happily run a ruleset that detects nothing.
Silent failures need differential testing. No error message means bisecting is your diagnostic.
Verify version-specific behavior yourself. What I found on 4.14.2 may differ on your release. Test rather than trust.
Documentation claiming deployment is not deployment. Reconcile what your docs say against what the manager is actually running.
Zero fires is a signal. Audit custom rules that never trigger instead of assuming they are waiting patiently.
Related: Deploying Fleet-Wide SIEM Across a Proxmox Cluster | Wazuh Agent Enrollment Across a Multi-VLAN Homelab | SIEM Automation Pipeline