Select the rule version, paste local rules, and click Optimize to receive optimization suggestions covering performance, correctness, and best practices.
Essential guidelines and best practices for writing Snort 3 local rules
A Snort 3 rule consists of a header and options. Compared to Snort 2, some keyword positions and syntax have changed.
action proto src_ip src_port direction dst_ip dst_port (options;)
# Example
alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (
msg:"My Custom Rule";
flow:to_server,established;
content:"/attack", depth 20, fast_pattern;
service:http;
sid:1000001; rev:1;
)
alert, drop, block, pass, log, rewritetcp, udp, icmp, ip, http, dns, smb, etc.-> (unidirectional) or <> (bidirectional)( ) and each item separated by ;gid, sid, rev must always appear at the end of the options blockLocal rule SIDs must be 1,000,000 or higher. Conflicting with the Talos-managed SID range can cause rules to be overwritten.
rev: is optional, but always increment by 1 when modifying a rulesid:1000001; rev:1; # ✓ correct — local rule range sid:1001; # ✗ dangerous — reserved by Talos
Rules without flow are evaluated against every packet, causing severe performance impact. Nearly every rule should specify flow.
# TCP traffic to server (web attack detection, etc.) flow:to_server,established; # TCP traffic from server (response detection) flow:from_server,established; # Bidirectional without connection tracking flow:stateless; # UDP (no established state) flow:to_server;
to_server / from_server: restrict traffic directionestablished: inspect only packets after TCP 3-way handshake — major performance gainnot_established: detect initial connection packets (SYN scans, etc.)no_stream: inspect individual packets before reassemblyIn Snort 3, content modifiers are inline — they are not written as separate options like in Snort 2.
# Snort 2 style (incorrect for Snort 3) content:"/admin"; depth:20; nocase; distance:0; # Snort 3 correct style — modifiers inline content:"/admin", depth 20, nocase, distance 0; # Hex pattern (binary protocol detection) content:"|0d 0a 0d 0a|"; # fast_pattern — designate the most unique pattern among multiple contents content:"X-Trigger-Pattern", fast_pattern; content:"/other", distance 0;
content options, assign fast_pattern to the longest and most unique patternnocase enables case-insensitive matching — has a performance cost, use only when necessarydepth/offset improves performancecontent:!"unwanted" — matches only when the pattern is absentIn Snort 3, HTTP inspection uses sticky buffers. The buffer keyword must be placed immediately before content or pcre.
# URI-based detection http_uri; content:"/cgi-bin/.%2e/", fast_pattern; # HTTP header inspection http_header; content:"Host: malicious.com"; # POST body inspection http_client_body; content:"payload=exploit"; # HTTP method restriction http_method; content:"POST"; # Status code detection (response detection) http_stat_code; content:"200"; # service keyword required — activates the HTTP inspector service:http;
| Sticky Buffer | Inspects | Snort 2 Equivalent |
|---|---|---|
| http_uri | Normalized URI | uricontent / pcre U flag |
| http_raw_uri | Raw (non-normalized) URI | pcre I flag |
| http_header | Full request/response headers | pcre H flag |
| http_client_body | Request body (POST body) | pcre P/D flag |
| http_method | HTTP method | pcre M flag |
| http_cookie | Cookie value | pcre C flag |
| http_stat_code | HTTP status code | pcre S flag |
PCRE is powerful but has very high CPU cost. Always precede PCRE with a content anchor to reduce evaluation frequency.
# ✗ Bad — PCRE alone with no content anchor (applied to every packet) pcre:"/union\s+select/i"; # ✓ Good — pre-filter with content, then PCRE content:"union", fast_pattern, nocase; pcre:"/union\s+select\s+/i"; # HTTP URI PCRE (Snort 3 sticky buffer style) http_uri; pcre:"/\/\.\.\/etc\/passwd/i"; # Snort 2 HTTP flags → Snort 3 conversion # ✗ Snort 2: pcre:"/pattern/Ui" (U = http_uri, i = case-insensitive) # ✓ Snort 3: http_uri; pcre:"/pattern/i"
U, I, H, D, P, C, M, S, Y, K) → all replaced by sticky buffers in Snort 3i, s, m, x) — standard PCRE flags, unchangedfast_pattern alongside PCRE dramatically reduces the number of PCRE evaluations.*, .+) risk ReDoSSnort 2's flowbits is replaced by xbits in Snort 3. xbits provides host-based state tracking with finer-grained control.
# Snort 2 (flowbits) flowbits:set,malware.stage1; flowbits:isset,malware.stage1; # Snort 3 (xbits) — track clause is required xbits:set,malware.stage1,track ip_src; xbits:isset,malware.stage1,track ip_src; xbits:unset,malware.stage1,track ip_src; # expire recommended — prevents memory leaks xbits:set,malware.stage1,track ip_src,expire 3600; # Rule 1: detect stage 1 and set bit alert tcp ... (content:"stage1"; xbits:set,attack.stage1,track ip_src,expire 60; sid:1000001;) # Rule 2: detect stage 2 only from IPs with bit set alert tcp ... (xbits:isset,attack.stage1,track ip_src; content:"stage2"; sid:1000002;)
track ip_src, track ip_dst, or track ip_pairexpire, a default timeout applies — always set it explicitlyflowbits:noalert → use the standalone noalert; option in Snort 3flowbits:reset → no equivalent in Snort 3 (removed)service declares which application-layer protocol the rule targets. This ensures Snort 3's inspector is correctly invoked to parse HTTP, DNS, and other protocols.
service:http; # Activate HTTP inspector (required when using sticky buffers) service:https; # HTTPS (HTTP inspection after TLS decryption) service:dns; # DNS inspector service:smtp; # SMTP inspector service:ftp; # FTP inspector service:ssh; # SSH service:http,dns; # Multiple services can be specified
http_uri, http_header, etc.) may not function without service:httpmetadata:service http → replaced by the standalone service:http option in Snort 3Snort 2's threshold has been removed in Snort 3. Use detection_filter for inline rate limiting.
# Snort 2 (threshold — removed in Snort 3)
threshold: type limit, track by_src, count 5, seconds 60;
# Snort 3 (detection_filter — inline as a rule option)
detection_filter:track by_src, count 5, seconds 60;
# Example: alert if same source triggers 5+ times within 60 seconds
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
msg:"Brute Force Attempt";
flow:to_server,established;
content:"POST /login", fast_pattern;
service:http;
detection_filter:track by_src, count 5, seconds 60;
sid:1000010; rev:1;
)
track by_src: based on source IP; track by_dst: based on destination IPevent_filter module in snort.luaThe following Snort 2 keywords have been removed or changed in Snort 3. Rules containing these keywords will fail to load or behave incorrectly.
| Snort 2 Keyword | Status | Snort 3 Replacement |
|---|---|---|
| uricontent | Removed | http_uri; content:"..." |
| rawbytes | Removed | raw_data; sticky buffer |
| flowbits | Replaced | xbits:action,name,track ip_src |
| threshold | Removed | detection_filter (inline) |
| fast_pattern:only | Removed | fast_pattern (no argument) |
| resp / react | Removed | rewrite action or DAQ module |
| tag | Removed | No equivalent |
| activates / activated_by | Removed | Dynamic rules not supported |
| logto / session | Removed | No equivalent |
| stream_reassemble | Removed | Moved to stream config in snort.lua |
Rules to follow when uploading local rule files to Cisco FMC (Firepower Management Center).
_); only letters, digits, ., _, -, + are allowed (no spaces, non-ASCII, or special characters).rules or .txtgid:1; explicitly or omit it)# Valid filename examples snort3_local_rules_20260408.rules ✓ local_rules_v2.rules ✓ my rules.rules ✗ (spaces not allowed) 한글룰.rules ✗ (non-ASCII not allowed) 123rules.rules ✗ (cannot start with a digit)
As the number of local rules grows, the performance impact increases. Following these guidelines ensures both detection accuracy and performance.
flow: Use flow:to_server,established to narrow the scope of packets evaluatedfast_pattern: Explicitly set it on the most unique pattern in multi-content rulesdsize: Limit payload size to skip unnecessary inspections$HTTP_PORTS) instead of anyclasstype: Helps with alert priority management and classification filtering# ✓ Optimized rule example
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
msg:"ET WEB Possible SQL Injection";
flow:to_server,established; # direction + state restriction
http_uri; # HTTP URI sticky buffer
content:"UNION", fast_pattern, nocase; # most unique pattern + fast_pattern
content:"SELECT", distance 0, nocase;
pcre:"/union\s+select/i"; # PCRE after content pre-filter
dsize:>10; # payload size filter
service:http;
classtype:web-application-attack;
sid:1000020; rev:1;
)
Offline Python 3 script to validate converted Snort 3 rules. No external dependencies required.
| Check | Severity | Description |
|---|---|---|
| Rule structure | ERROR | Valid header and option parentheses |
| Required fields | ERROR | msg, sid must be present (rev is optional) |
| Direction operator | ERROR | Must be -> or <> |
| Deprecated Snort 2 keywords | ERROR | uricontent, rawbytes, threshold, resp, react, tag, activates, activated_by, logto, session |
| fast_pattern:only | ERROR | Removed in Snort 3 — use plain fast_pattern |
| PCRE HTTP flags | ERROR | Uppercase flags (U, I, H, D, P, C, M, S, Y, K) must be converted to sticky buffers |
| metadata:service | WARN | Should be extracted as standalone service: option |
| gid/sid/rev order | WARN | Should appear at end in gid → sid → rev order |
| Unusual action/protocol | WARN | Flags non-standard action or protocol values |
Weekly Snort 2 Subscriber Rule Update (SRU) — new, modified, and deleted rules sourced from the Talos advisory on Snort.org
Weekly Snort 3 Lightweight Security Package (LSP) — new and modified rules sourced from the Talos advisory on Snort.org
| Snort 2 Keyword | Action | Snort 3 Result |
|---|---|---|
| uricontent:"X" | Split into sticky buffer + content | http_uri; content:"X" |
| flowbits:set,name | Converted to host-tracked xbits | xbits:set,name,track ip_src |
| flowbits:noalert | Standalone flag conversion | noalert |
| flowbits:reset | No Snort 3 equivalent — removed + WARNING | ⚠ removed |
| isdataat:N,rawbytes | rawbytes sub-option stripped | isdataat:N |
| file_data:mime | Parameter stripped (Snort 3 uses plain file_data) | file_data |
| fast_pattern:only | :only qualifier removed (not valid in Snort 3) | fast_pattern |
| metadata:service http | service entries promoted to standalone option | service:http |
| sameip | Renamed | same_ip |
| rawbytes, threshold, resp, react, tag, activates, activated_by, logto, session, stream_reassemble, replace | Removed from Snort 3 — dropped with WARNING comment | ⚠ removed |
http_uri, http_header) must appear before its associated content option. This pass scans the option list and moves any sticky buffer that appears after a content keyword to the correct position immediately preceding it./pattern/Ui for URI, /pattern/Hi for header). Snort 3 replaces these with sticky buffers placed before the pcre option. This pass reads each pcre's flag string, maps it to the correct sticky buffer, inserts the buffer immediately before the pcre, and strips the HTTP flag from the PCRE modifier string.depth, within, offset, distance, nocase, fast_pattern) follow a content option as separate semicolon-delimited options. Snort 3 requires these to be inlined as comma-separated arguments inside the content option itself. This pass merges consecutive modifier options into the preceding content option.gid, sid, rev are always sorted to the end of the options in that order. If a Start SID is specified, each rule's SID is remapped sequentially from that value, and the original→new SID mapping is persisted to localStorage for cross-session traceability.
validate_snort3.py script for offline rule validationShare your suggestions, bug reports, or ideas. Posts are visible to all visitors.