Getting to know Wazuh decoders and rules: the structure of local_decoder and local_rules, syslog and JSON log formats, how to create custom decoders and custom rules, the match, filter, syscheck, vulnerability, and scan rule types, plus the level scale from 0 to 15 for prioritizing alerts.

In episode 5 you learned to monitor and analyze logs from an agent already connected to the manager: viewing raw events, browsing with queries, and building aggregations to find anomalies. At that point the big question was still open: how does Wazuh decide that an event deserves to become an alert.
Episode 6 answers that question by dissecting the two core detection components: decoders and rules. Decoders turn raw logs into structured data, then rules determine whether that data is suspicious. We'll cover the structure of local_decoder.xml and local_rules.xml, syslog and JSON formats, how to create custom decoders, the various rule types, and the level scale from 0 to 15 as the language of alert priority.
By the end of this episode you'll not only be able to read XML rules, but also write your own decoders and rules for applications that have no built-in integration. That's a skill that makes Wazuh far more useful in real environments, because there's always an internal application whose log format only your own team understands.
Before diving into syntax, we need to understand where decoders and rules sit in the Wazuh workflow. Events flow from the collector on the agent to the manager through several stages: decoding, rule matching, then storage and alerting. The decoding stage normalizes the log; the rule matching stage evaluates the result of that normalization.
Built-in decoder files are stored in the /var/ossec/etc/decoders/ directory on the manager, while built-in rules live in /var/ossec/etc/rules/. When you want to add your own logic without touching the built-in files, the right place is local_decoder.xml and local_rules.xml. Both are guaranteed to be read by Wazuh and are safe to edit.
Info
Wazuh evaluates decoders in declaration order. The first decoder that matches the prematch is used and stops any further search. That's why the most specific decoders should be placed earlier, so they don't get swallowed by generic decoders like syslog.
Wazuh decoders are written in XML and always begin with a decoder element with a name attribute. The three child elements most commonly used: prematch for quick filtering, regex to extract fields, and order to name the extracted results in sequence.
<decoder name="custom-syslog">
<prematch>^syslog: </prematch>
<regex>^syslog: \s+(\S+): (.*)$</regex>
<order>program_name, log</order>
</decoder>In the example above, prematch ensures only logs that start with the word syslog are handled by this decoder. regex then captures two parts: the program name and the message content. order labels the first part as program_name and the second as log. These named fields are what the rule later reads.
Note that prematch is optional but highly recommended. Without prematch, Wazuh runs the regex against every event, which wastes resources on large-scale managers. With a selective prematch, the regex is only evaluated for relevant events, and other decoders are skipped faster.
The two log formats you'll encounter most often are syslog and JSON. The traditional syslog format stores facility, severity, timestamp, hostname, and message in a single text line. Wazuh already has built-in decoders for this format, so logs from rsyslog or syslog-ng are recognized without any extra configuration.
For modern applications, JSON is far more common because its structure is tidy and easy to parse. Wazuh handles JSON logs by matching a prematch of the opening curly brace, then automatically mapping each top-level key into dynamic fields.
{"event":"login","user":"arman","src_ip":"192.168.1.20","status":"failed"}From the single JSON line above, Wazuh automatically provides fields named event, user, src_ip, and status. Rules can check those field values directly without needing extra regex. This makes writing rules for JSON-format applications far faster than for plain text logs.
Suppose your team runs a billing application that writes logs like billing-app: user arman invoice 1042 failed. No built-in decoder recognizes it, so we'll create our own. The strategy is two-layered: the first decoder recognizes the program name, the second extracts the fields inside its message.
<decoder name="billing-app">
<program_name>^billing-app</program_name>
</decoder>
<decoder name="billing-app-fields">
<parent>billing-app</parent>
<regex>^user (\S+) invoice (\d+) (.*)$</regex>
<order>user, invoice_id, action</order>
</decoder>The second decoder uses the parent attribute to attach to the first decoder. The regex captures three fields: user, invoice_id, and action. After saving the changes, test this decoder using wazuh-logtest from the manager terminal:
wazuh-logtest
billing-app: user arman invoice 1042 failedIf the regex pattern matches, wazuh-logtest shows the decoded fields along with any matching rule. If there's no rule, it shows a message that the event wasn't recognized. Once you're happy with the result, restart the manager so the decoder changes take effect: systemctl restart wazuh-manager.
Once the data is structured, rules make the decision. Rules are written in XML with id and level attributes. The rule body contains conditions, and all listed conditions must be met for the rule to fire. The most common conditions are match, decoded_as, field, and group.
<rule id="100001" level="8">
<decoded_as>billing-app-fields</decoded_as>
<match>failed</match>
<description>Pembayaran berstatus failed</description>
<group>billing,</group>
</rule>The rule above only fires if the decoded event comes from the billing-app-fields decoder and contains the word failed. When both conditions are met, Wazuh generates a level 8 alert. The description attribute becomes the alert's title in the dashboard, while group provides a classification label for cross-rule correlation.
For JSON logs, conditions can be written directly against dynamic fields using the field element with a name attribute, for example comparing the src_ip value against a specific address. This is far more expressive than guessing text patterns, and is the main reason many teams prefer the JSON format.
Wazuh divides rules into several categories based on their function. Understanding the categories helps you read the built-in ruleset and choose the right pattern for your own needs.
if_sid to be a child of another rule.syscheck group.vulnerability-detector group.attack and recon groups.An example filter rule that suppresses routine attempt events:
<rule id="100010" level="0">
<if_sid>100001</if_sid>
<match>recurring-test</match>
<description>Event percobaan rutin, diabaikan</description>
</rule>Because its level is 0, events matching this rule generate no alert at all, while also replacing the parent rule 100001 for those events. This technique is very useful for dampening false positives from activity that is genuinely intentional, such as periodic health checks.
Level is the universal language of priority in Wazuh. The level determines how serious an event is, and is the basis for dashboards, filters, and even active response triggers. Here's the range to remember:
The level you assign must be consistent with its consequences. A high level isn't just a display warning — it also determines what the team sees first, how fast automation responds, and how much noise is generated.
Info
Start with conservative levels: give level 3 to successful events, 4 to 5 for suspicious ones, and 8 and above for genuinely dangerous indications. Setting levels too high for everything just makes the team numb to alerts.
The group attribute gives a second dimension to a rule alongside the level. Through groups, a rule can be tied to a security taxonomy: authentication_failed for login failures, syscheck for file integrity, attack for attack patterns, all the way to compliance standards like gdpr and pci_dss.
A single rule can be registered in several groups at once by separating them with commas. These groups are what the compliance dashboard later uses to count how many alerts relate to a regulation. So assigning groups carefully from the start will save a lot of time when facing an audit.
Prioritizing alerts in a SOC team is usually a combination of these two axes: the level determines urgency, the group determines the escalation category. For example, all authentication_failed group events with level 8 or above are forwarded directly to an analyst, while level 5 events just enter the daily review queue.
This episode closes the gap between raw logs and security decisions. You now understand that the decoder is the normalization layer that turns logs into fields, while the rule is the evaluation layer that turns fields into alerts. Custom decoders and custom rules open the way to detecting whatever is unique in your environment.
Key takeaways:
local_decoder.xml and tested with wazuh-logtest before restarting the manager.decoded_as, match, and field conditions to produce decisions.In episode 7 we move to File Integrity Monitoring: how Wazuh watches important file changes in real time and on a schedule, distinguishes legitimate and suspicious files, and reports every addition, modification, and deletion. See you there!