Home/ Blog/ Explainers/ Article
Blog · Explainers

How to Use wazuh-logtest to Debug Wazuh Decoders and Rules

How to use wazuh-logtest to test Wazuh decoders and rules: run the CLI, read all three phases, use verbose mode, and fix a rule that will not fire as expected.

One log line passing through the Wazuh decode and rule engine into a single lit alert

wazuh-logtest is the Wazuh server tool that shows how one log line would be processed before any real alert depends on it. Run /var/ossec/bin/wazuh-logtest on the server, paste a sample line, and it prints which decoder matched, which fields it pulled out, and which rule fired and at what level. It is the fastest way to prove a detection works, or to find out why it does not.

Think of it as a flight simulator for your detections. Instead of waiting for a real attack to discover that a rule was wrong, you fly one exact log line through the same engine the server uses and watch what happens at each step. When a detection you expected never shows up, this is the tool that tells you whether the problem is the decoder, the rule, or your own test.

This guide assumes Wazuh is already running. If it is not, start with our walkthrough on how to use Wazuh, then come back to make your detections trustworthy. We run Wazuh as a managed service, so the workflow below is the one our detection engineers reach for every time a rule behaves in a way they did not expect.

What the three phases are telling you

Every line you paste goes through three phases, and reading them in order is the whole skill. Phase 1, pre-decoding, pulls the outer envelope that standard log formats share: when the event happened, which host it came from, and which program wrote the line. Phase 2, decoding, runs the matching decoder to extract the fields inside the message, such as the username and the source IP. Phase 3, rule matching, takes those fields and checks them against the ruleset to decide whether anything is raised, and how serious it is.

Raw log lineyou paste one linePhase 1: pre-decodetime, host, programPhase 2: decodefields: user, srcipPhase 3: rulesrule id + levelAlertor nothing
How wazuh-logtest walks one log line: pre-decoding pulls the envelope, decoding extracts the fields, rule matching decides if an alert is raised. If a later phase has nothing to work with, it stays silent.

The reason this matters: a rule can only test fields that a decoder actually produced. If Phase 2 comes back empty, no amount of rule tuning in Phase 3 will help, because the field the rule is looking for does not exist yet. Most "my rule will not fire" problems are really Phase 2 problems wearing a Phase 3 costume, and wazuh-logtest is what lets you see the difference in seconds.

Running wazuh-logtest, and the one flag that matters

Start a session on the server and paste a line at the prompt. The tool keeps the session open so you can paste more lines, which matters for rules that count repeats. Press Ctrl+C to exit.

terminal · start a session on the Wazuh server
$ sudo /var/ossec/bin/wazuh-logtest
Starting wazuh-logtest v4.x
Type one log per line
>> Oct 15 10:22:14 web01 myapp: Failed login for user 'admin' from 203.0.113.50

The session holds state. A counter called firedtimes tracks how often each rule has matched since you started, which is how you exercise a rule that only escalates after several hits. Exit the session and that count resets to zero.

There are a handful of flags, but one earns its keep above the rest. The Wazuh documentation calls -v, verbose mode, the key option for troubleshooting a rule or decoder problem, and that is exactly right: it prints the decoders and rules that were considered for the line, not just the final match, so you can see where the chain broke.

FlagWhat it doesWhen you reach for it
-vPrints the verbose result, including the decoders and rules that were evaluated for the line.Almost always. This is the one flag that answers why a rule did or did not fire.
-dDebug output from the analysis engine itself.Deep decoder problems, when even verbose does not explain the parse.
-qQuiet: suppresses the banner and prompts.Scripting, or feeding lines in from a pipe.
-U rule:level:decoderReturns a zero exit status only if the line produces that exact rule id, level, and decoder.Turning a known-good line into a pass or fail check you can automate.
Source: Wazuh wazuh-logtest tool reference. The documentation calls -v the key option for troubleshooting a rule or decoder.

Worked example: a log that will not decode

Here is the situation that sends most people to this tool. You have a custom application, call it myapp, that logs failed logins, and you want an alert when one source hammers it. You wrote a rule, but nothing fires. Paste a real line and watch what the tool says.

output · before the decoder exists
**Phase 1: Completed pre-decoding.
	full event: 'Oct 15 10:22:14 web01 myapp: Failed login for user 'admin' from 203.0.113.50'
	timestamp: 'Oct 15 10:22:14'
	hostname: 'web01'
	program_name: 'myapp'
**Phase 2: Completed decoding.
	No decoder matched.
**Phase 3: Completed filtering (rules).
	No rule matched.

Phase 1 worked: it read the timestamp, the host web01, and the program myapp. But Phase 2 says no decoder matched, so the username and the IP were never extracted. That is the root cause. Your rule keys on a srcip field that does not exist, so it can never match. The tool found the problem in one paste.

The fix is a decoder. A parent decoder claims the lines from this program, and a child decoder uses a regular expression to lift the fields you care about into named values. Custom decoders live in local_decoder.xml.

/var/ossec/etc/decoders/local_decoder.xml · on the server
<decoder name="myapp">
  <program_name>^myapp</program_name>
</decoder>
<decoder name="myapp-login">
  <parent>myapp</parent>
  <regex type="pcre2">Failed login for user '(\w+)' from ([\d.]+)</regex>
  <order>user, srcip</order>
</decoder>

The parent matches on the program name. The child runs its pattern and maps the two capture groups, in order, to the field names user and srcip. Those names are the contract between the decoder and the rule, so they have to match on both sides. Now add the rules that use them. Custom rules live in local_rules.xml and use IDs of 100000 or higher so they never collide with the built-in set.

/var/ossec/etc/rules/local_rules.xml · on the server
<group name="local,myapp,">
  <rule id="100200" level="5">
    <decoded_as>myapp</decoded_as>
    <description>myapp: failed login for $(user) from $(srcip)</description>
  </rule>
  <rule id="100201" level="10" frequency="6" timeframe="120">
    <if_matched_sid>100200</if_matched_sid>
    <same_source_ip />
    <description>myapp: repeated failed logins from one source IP</description>
  </rule>
</group>

The first rule simply records a decoded failed login at a quiet level 5. The second watches for the pattern: it escalates to level 10 when the first rule matches six times inside two minutes from the same source IP. The same_source_ip tag scopes the count to one attacker, so six unrelated failures from six different hosts do not trip it, but one machine hammering the box does.

Edits to these files are not live until the server reloads them, so restart the manager with sudo systemctl restart wazuh-manager, then start a fresh wazuh-logtest session and paste the same line again.

output · after restart, same line re-tested
**Phase 2: Completed decoding.
	name: 'myapp'
	user: 'admin'
	srcip: '203.0.113.50'
**Phase 3: Completed filtering (rules).
	Rule id: '100200'
	Level: '5'
	Description: 'myapp: failed login for admin from 203.0.113.50'

Phase 2 now shows the myapp decoder with user and srcip filled in, and Phase 3 reports rule 100200 at level 5. Now paste the same line again, several times, in that session and inside the two-minute window. Watch the firedtimes counter climb with each paste; once it crosses the rule's threshold, the escalation fires instead.

output · once the counter crosses the threshold
**Phase 3: Completed filtering (rules).
	Rule id: '100201'
	Level: '10'
	Description: 'myapp: repeated failed logins from one source IP'
	firedtimes: 6

That is the whole loop: decode, match, tune, re-test. Run the session again with -v and you will also see the rule chain the engine walked to get there, which is how you confirm your rule, and not some broader built-in, is the one doing the work.

Why your rule did not fire: a quick checklist

Almost every "it will not fire" case is one of a short list, and each has a tell in the output. Read Phase 2 first to confirm the fields exist, then read Phase 3 for the rule decision. The table maps the symptom you see to the cause and the fix.

What you see in the toolLikely causeThe fix
Phase 2 shows no decoder, or your fields are missingNo decoder matched the line, so the field your rule keys on was never created.Write or correct a decoder; confirm the field names in your decoder order match what the rule checks.
Decoder matches but the rule still does not fireThe rule names a field the decoder did not extract, or a value that does not match.Compare the rule field against the exact field shown in Phase 2; names and values must line up.
A different rule fires than the one you wroteA parent or more general rule matched first, or your rule is missing its if_sid link.Read the verbose output for the rule chain; add the right if_sid or if_matched_sid.
The rule fires but never raises an alertThe rule is level 0, which Wazuh treats as ignored by design.Give it a level of 1 or higher, or keep it at 0 on purpose to silence known noise.
A frequency rule never escalatesYou did not paste enough lines inside the session, or the timeframe elapsed.Paste the line enough times in one session and inside the timeframe; watch the firedtimes counter climb.
You edited a file and nothing changedThe running ruleset is still the old one.Restart wazuh-manager, then start a fresh wazuh-logtest session so it loads your change.
The six failures that cover almost every "my rule will not fire" ticket, and what wazuh-logtest shows for each.

The habit worth building: before you blame the rule, confirm the decoder. If the field is not in Phase 2, the rule was never going to work, and you have just saved yourself an hour of editing the wrong file.

Where wazuh-logtest stops

The tool tests logic, not plumbing, and it is honest to know the difference. It proves that a given line, if it reaches the server, decodes and matches the way you expect. It does not prove that your agent is actually shipping that log, that the real-world format matches your sample byte for byte, or that an active-response script or integration downstream will behave. Treat it as a bench test for detections, then confirm the end to end path with a real event in the dashboard.

Two more edges catch people. First, the frequency counter lives in the session, so a correlation rule that depends on timing behaves differently at the prompt than under real traffic; the tool shows you the logic is sound, not that the timing will line up in production. Second, it reads the ruleset the manager has loaded, so an edit you have not restarted into the manager simply will not appear. The dashboard offers the same test under its developer tools, but the command line with -v stays the reliable path when you are actually debugging.

Used this way, wazuh-logtest turns detection engineering from guesswork into something you can check. Write the decoder, test it, write the rule, test it, and only then trust it in the live pipeline. If you would rather have that tuning done for you, our team runs it as part of managed Wazuh, and the same instinct carries into the Wazuh API and into why we built on Wazuh in the first place.

Topics

Frequently asked questions

What is wazuh-logtest used for?

wazuh-logtest tests how the Wazuh server would process a log line. You paste a sample line and it shows which decoder matched, which fields it extracted, and which rule fired, so you can validate a detection or debug one that is not working, without waiting for live traffic.

How do I run wazuh-logtest?

Run sudo /var/ossec/bin/wazuh-logtest on the Wazuh server, then paste a log line at the prompt. The session stays open so you can paste more lines, which is needed to test rules that count repeats. Press Ctrl+C to exit.

Why is my Wazuh rule not firing in wazuh-logtest?

Most often the decoder did not extract the field the rule needs, so Phase 2 comes back empty and the rule has nothing to match. Other causes are a rule set to level 0 (ignored), a missing if_sid link to a parent rule, or a frequency rule that did not reach its count inside the session.

What does the -v flag do in wazuh-logtest?

The -v flag turns on verbose output, which lists the decoders and rules that were evaluated for the line rather than only the final match. The Wazuh documentation calls it the key option for troubleshooting a rule or decoder problem.

Do I need to restart Wazuh after editing a decoder or rule?

Yes. wazuh-logtest uses the ruleset the manager has loaded, so after editing local_decoder.xml or local_rules.xml you must run sudo systemctl restart wazuh-manager and start a fresh wazuh-logtest session for the change to take effect.

Does wazuh-logtest test the whole detection pipeline?

No. It tests decode and rule logic for a single line. It does not confirm that an agent is actually shipping that log, that real events match your sample exactly, or that a downstream active-response script will run. Use it as a bench test, then verify the full path with a real event.

See what Suriq finds on your own servers.

Agentless checks for uptime and your cloud, a lightweight agent for detection on each server, and one console that ranks and explains what it finds.