Network Rules.
Suricata & Snort, One Syntax Mostly.
A static reference for writing network IDS/IPS rules: header anatomy, sticky buffers versus the deprecated content-modifier syntax, the detection keywords that do the real work, and the judgment calls that keep a rule from going blind against encrypted traffic. No rule engine here, no live testing, just the reference. For the port/service context behind a rule's dport/sport match, see the Protocol & Port Reference.
Rule Anatomy
Action, header, options. Suricata and Snort share this shape; Suricata's app-layer protocols and sticky buffers are the parts that moved on.
The Three Parts, In Order
1. Action alert | drop | reject | pass ├── alert: logs and generates an event, never blocks traffic ├── drop (IPS mode only): blocks and logs, silently └── reject: blocks and logs, and sends a TCP RST or ICMP unreachable back // Suricata runs IDS and IPS from the same binary; Snort needs inline mode configured for drop/reject to block 2. Header protocol src_ip src_port -> dst_ip dst_port ├── protocol: tcp, udp, icmp, or an app-layer protocol like http, tls, dns ├── src_ip / dst_ip: an IP, a CIDR block, a variable ($HOME_NET, $EXTERNAL_NET), or a negation (!$HOME_NET) └── -> is directional; <> matches traffic in either direction // $HOME_NET / $EXTERNAL_NET are defined once in suricata.yaml or snort.conf, not re-typed per rule 3. Options (everything inside the parens) (msg:"..."; content:"..."; sid:1000001; rev:1;) ├── every option ends in a semicolon; order matters for sticky buffers, not much else ├── msg, sid, rev, classtype, reference are metadata and required bookkeeping └── content, pcre, flow, and sticky buffers like http.uri are the actual matching logic // sid must be unique across the whole ruleset; local rules conventionally start at 1000000+
Sticky Buffers
A sticky buffer sets which part of the traffic every content/pcre keyword after it inspects, until the next sticky buffer or the end of the rule.
Modern Sticky Buffer Syntax
http.uri; content:"/gate.php";— matches only inside the request URI, not the whole packethttp.host; content:"evil.example";— matches only the HTTP Host headerhttp.user_agent; content:"curl";— matches only the User-Agent header value- Everything after a sticky buffer keyword stays scoped to it until another sticky buffer appears
Deprecated Content Modifiers
content:"/gate.php"; http_uri; puts the modifier after the content match instead of before it. Suricata still accepts this form, but every current ruleset and the official documentation write new rules with sticky buffers instead — write new rules the modern way and only expect to read the old form in inherited rulesets.Common Buffers Worth Knowing
tls.sni— the SNI hostname from a TLS ClientHello, readable even though the session is encrypteddns.query— the queried domain name in a DNS requestfile.data— reassembled, decompressed file content extracted from the stream (HTTP body, SMB file transfer, etc.)
Detection Keywords
The options that decide whether a rule fires, and how expensive it is to evaluate on every packet.
content & nocase
content:"malware"; nocase; is the cheapest and most common match. Binary bytes can be mixed in with pipe syntax, e.g. content:"|4D 5A|"; for the MZ header. Without nocase, the match is case-sensitive.pcre
pcre:"/pattern/i"; is far more expressive than a fixed string but costs more CPU per packet. Pair it after a content match narrows the candidate packets first, rather than running pcre against every packet on the wire.flow
flow:established,to_server; only inspects packets in an established session flowing toward the server. Direction values are to_server, to_client, from_client, from_server — matching the wrong direction is one of the most common reasons a rule never fires.flowbits
flowbits:set,seen_login; on one rule and flowbits:isset,seen_login; on a later one builds multi-stage detection logic, where a second rule only fires because an earlier rule already matched something in the same session.fast_pattern
fast_pattern; on the longest, least-common string in the rule, not necessarily the first one written, since a short or common string makes a poor pre-filter.threshold / detection_filter
threshold:type limit, track by_src, count 5, seconds 60; only alerts after 5 matches from the same source within a minute. Built for patterns like brute-force login attempts, where every individual match is genuine but alerting on each one drowns the analyst.Best Practices
The habits that keep a network rule fast, accurate, and maintainable once someone else inherits it.
Write New Rules With Sticky Buffers
http.uri, tls.sni, and the other sticky buffer keywords in anything new you write. The old content:"x"; http_uri; form still parses, but it's the legacy syntax, not the one to reach for by default.Put fast_pattern On The Most Selective Content
content:"GET"; makes a poor fast_pattern candidate since it pre-filters almost nothing.Anchor Every Rule With flow
flow:established,to_server; (or whichever direction actually applies) cuts both the CPU cost and the false-positive surface, since the rule stops inspecting traffic it was never meant to match.Give Every Rule A Real classtype And reference
reference:url,attack.mitre.org/techniques/T1059/001/; maps a hit straight to an ATT&CK technique.Test Against A Clean Pcap Before Deploying
suricata -r sample.pcap -S your.rules -l /tmp/out confirms a rule fires, or doesn't, against a known pcap without touching live traffic or an existing production ruleset.Keep A Dedicated sid Range For Local Rules
Common Pitfalls
Mistakes that don't show up as an error, just as a rule that silently never fires or fires on everything.
Missing Flow Direction
flow:to_client; will also inspect client-to-server traffic, doubling both the match surface and the false-positive risk.content Without nocase On Case-Varying Input
content:"POST"; can silently miss a lowercase or mixed-case variant that a real client or a deliberately evasive one sends.Encrypted Payloads Defeat content/pcre Entirely
tls.sni or a JA3/JA4 TLS fingerprint.Duplicate Or Reused sid Values
sid. Always check for collisions before deploying custom rules alongside a vendor ruleset like ET Open.Full Example
Every keyword from the sections above, in one rule, doing real work.
Suspicious PowerShell EncodedCommand Over HTTP POST
alert http $HOME_NET any -> $EXTERNAL_NET any ( msg:"SUSPICIOUS PowerShell EncodedCommand over HTTP POST"; flow:established,to_server; http.method; content:"POST"; nocase; http.uri; content:"/gate.php"; fast_pattern; nocase; file.data; content:"-EncodedCommand"; nocase; classtype:trojan-activity; reference:url,attack.mitre.org/techniques/T1059/001/; sid:1000042; rev:1; ) // http.method, http.uri, and file.data are sticky buffers — each content: that follows applies // to the buffer named just before it, not the raw packet payload. fast_pattern sits on the URI // content because it's the most unique string in the rule, so Suricata's matcher indexes on it first.