2026年11月14日
2026年11月14日
How to Write Snort Rules: Syntax, local.rules and Testing
This article is a localized edition of the Japanese original. Read the Japanese original
A custom Snort rule is written in two parts, a header (what, and from where to where) and options (what to match and how to record it), and the rules are collected in local.rules and loaded from the configuration file. If you follow the order of passing a syntax check and a pcap replay test before putting a rule into production, you can find missed detections and false positives early. This article assumes Snort 3 and explains the rule syntax, practical rules using WordPress as an example, and the stumbling points in testing and operation.
Prerequisite: differences between Snort 3 and Snort 2
Snort 3 is the current mainstream version (as of October 2026). Official support for the Snort 2 series is shrinking, so Snort 3 is the safer choice for a new installation. The basic form of the rules is the same in both, but they differ in the following ways.
- The configuration file changed from snort.conf to snort.lua, in Lua format
- The way of specifying HTTP URIs and headers changed from "modifiers" to "sticky buffers" (described below)
- In Snort 3 you can split the inside of the rule's parentheses across multiple lines (in Snort 2 a
\is required at the end of each line)
Many explanations online are still written in Snort 2 syntax, so check which syntax they use before copying.
Basic structure of a rule
One rule expresses one detection condition. Here is a minimal example.
alert icmp any any -> $HOME_NET any (msg:"LOCAL ICMP echo request"; itype:8; sid:1000001; rev:1;)
What comes before the parentheses is the header, and what is inside the parentheses is the options.
Header
The header is arranged in the order "action protocol source-IP source-port direction destination-IP destination-port."
- Action: Start by writing
alert(detect and log).dropandrejectmake sense only when Snort runs inline (in the path of the traffic); in a monitoring-only setup they cannot stop traffic - Protocol:
tcp,udp,icmp,ipand so on. In Snort 3 there is also a syntax that uses a service name such ashttp - IP and port: You can use
any,192.0.2.0/24, or variables such as$HOME_NET. Variables are defined in snort.lua (or in the default-value file it loads) - Direction:
->is one-way and<>is bidirectional
Many environments leave $HOME_NET set to any. That makes it impossible to tell inside from outside and changes what the rules mean, so set the range of your own network first.
Options
Options are written in the form key:value;, and the final semicolon is never omitted. These are the commonly used ones.
msg: The description shown in the alert. Write a sentence that the person reading the log later will understandsid: The rule's identification number. Numbers below 1,000,000 are reserved for distributed rules, so use 1000001 and above for custom rulesrev: The rule's revision number. Increase it by one when you change the contentcontent: A string contained in the packet payload.nocasemakes it case-insensitiveflow: Narrows the direction and state of the connection, as into_server,establishedclasstype: The classification. It also affects the default priority
A rule using WordPress as the example
Example: Detect a burst of POSTs to wp-login.php
This example detects brute-force attempts against the login screen as a short-term burst from the same source.
alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (
msg:"LOCAL WordPress wp-login.php POST burst";
flow:to_server,established;
http_method; content:"POST";
http_uri; content:"/wp-login.php", nocase;
detection_filter:track by_src, count 10, seconds 60;
classtype:attempted-user;
sid:1000010; rev:1;
)
http_method; and http_uri; are sticky buffers, which mean the content that follows is matched within the "method" or "URI" scope. In Snort 2 you attached a modifier after it, as in content:"/wp-login.php"; http_uri;. detection_filter is the condition to raise an alert "when there are more than 10 from the same source within 60 seconds."
You cannot see inside HTTPS traffic
This is the point where people most often get stuck. Snort cannot read the contents of encrypted traffic. If the site uses HTTPS and Snort sits between the internet and the server, the URI matching in the rule above will not match.
The countermeasure depends on your setup.
- Terminate TLS at a load balancer or reverse proxy, and monitor the plaintext segment behind it with Snort
- Limit yourself to rules that can be written with header information such as connection counts, ports and source IPs, rather than contents
- Leave application-layer detection to a WAF or server logs (access logs, fail2ban and so on)
If you draw a diagram of which segment of traffic Snort is watching before deciding the rule policy, you avoid waste.
Tips for making rules run fast
- Include at least one
content. Snort narrows down candidates bycontentbefore checking the detailed conditions, so a rule with onlypcreis slow - Choose a long, uncommon string for
content. With only a short string such asGET, almost all traffic becomes a candidate - Narrow the direction with
flow. Many rules do not need to inspect the response-side traffic
Add it to local.rules and load it
Collect custom rules in a file separate from the distributed rules (for example local.rules). This prevents them from being overwritten by updates to the distributed rules and lets you list the ones you wrote.
Load the rule file in the ips section of snort.lua. Change the path to match your environment.
ips =
{
variables = default_variables,
include = '/usr/local/etc/rules/local.rules',
}
If you already load the distributed rules through rules or similar, you add local.rules to that setting. Back up the configuration file before changing it, and then check where and what is being loaded.
Test it
1. Syntax check
Check that the configuration and rules can be loaded before starting in production.
snort -c /usr/local/etc/snort/snort.lua -T
If an error appears, look at the rule at the line number shown. Common causes are a forgotten semicolon, an unclosed quotation mark, and a duplicate sid.
2. Replay a pcap to confirm the match
Before trying on production traffic, confirm with a pcap file that records the traffic you want to detect. Generate the access in a test environment and save it with tcpdump.
sudo tcpdump -i eth0 -w test.pcap port 80
snort -c /usr/local/etc/snort/snort.lua -R /usr/local/etc/rules/local.rules \
-r test.pcap -A alert_fast -k none
-r reads the pcap, and -A alert_fast prints alerts to the screen one per line. -k none skips checksum verification, so detection also works on a pcap recorded in an environment where the network card fills in checksums afterward.
There are two things to check: that an alert fires on the traffic you want to detect, and that none fires on normal everyday traffic (admin screen operations, plugin updates and so on). If you put a rule into production without checking the latter, a flood of false-positive alerts buries the real anomalies.
3. After going into production, watch it with alert first
Even in an inline setup, run with alert for a few days at first, and switch to drop after looking at the count and content. Find false positives that would block legitimate users while you are only detecting.
Stumbling points in operation
- Conflicts between rule updates and custom rules: Distributed rules (such as the Talos rules) are generally updated with a tool such as PulledPork 3. If you keep custom rules in a separate file, updates will not erase them
- Managing sids: Keep a record of who wrote what and why, in
msgand in a separate record (such as Git). If you change the content without increasingrev, people reading the logs get confused - Suppressing false positives: When you want to exclude only a specific IP, consider whether Snort's suppress settings can handle it before rewriting the rule
- Log volume: If there are too many alerts, narrow the rule's conditions or adjust how alerts are raised with
detection_filterand similar
Summary
- A rule is written with a header (action, protocol, IP, port, direction) and options (msg, content, sid, rev and so on). The sid of custom rules starts at 1000001
- In Snort 3 you specify the matching scope with sticky buffers. Do not mix it with Snort 2 syntax
- You cannot see inside HTTPS, so decide which segment Snort monitors before writing rules
- Check syntax with
-T, and use pcap replay to confirm that what should be detected is detected and that normal traffic does not trigger it, before going into production - In production, run with alert first, eliminate false positives, and then move on to blocking
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)