Suricata Rule Writing in Practice: Building IDS Rules from Scratch
Covers Suricata's three-part rule syntax and three detection rules for SQL injection, abnormal SSH, and malicious domains. Includes syntax checks, load, replay triggering, false-positive tuning, FAQ, and verification commands.
Scope
Enterprise intranet or cloud servers that need real-time monitoring of network traffic for intrusion without changing the application: scan probes, SQL injection, brute force, malware C2, and similar. Suricata is an open-source IDS/IPS engine with multithreading, protocol parsing, and custom rules. Deploy it on a gateway TAP/SPAN (mirror port) or at the server edge; it emits alerts for the security team. This article covers Suricata rule syntax from scratch so you can write working IDS rules.
Prerequisites
- Linux server (Ubuntu/Debian or CentOS), 2 cores / 4 GB RAM or more
- Suricata installed (6.x/7.x in this article): `sudo apt install suricata` (Ubuntu 22.04+)
- Internet access (needed for rule updates); basic TCP/IP and HTTP knowledge
Mechanism
A Suricata rule (signature) describes what traffic to match and what action to take. A rule has three parts: action + header + options, for example:
alert tcp $HOME_NET any -> $EXTERNAL_NET any \
(msg:"Example rule"; content:"test"; sid:1000001; rev:1;)
`alert` is the action (alert/drop/reject). `tcp $HOME_NET any -> $EXTERNAL_NET any` is the header (protocol, source, destination). Options are in parentheses: `content` matches payload, `msg` is the alert message, `sid` is the unique rule ID (custom rules should start at 1000000 to avoid colliding with official rules), `rev` is the revision. Suricata also has application-layer keywords for HTTP, DNS, TLS, and similar (`http.uri`, `dns.query`), which are more precise than raw byte matching.
Procedure
1. Confirm install and version
suricata --build-info | head -n 5
sudo suricata --list-keywords > /dev/null 2>&1; echo "keyword list ok"
2. Create a local rules file
sudo mkdir -p /etc/suricata/rules
sudo nano /etc/suricata/rules/local.rules
3. Write three typical rules
Rule 1: Detect SQL injection patterns in HTTP requests (union select / common injection functions):
alert http $EXTERNAL_NET any -> $HOME_NET any \
(msg:"ET LOCAL SQLi - union select in URI"; \
flow:established,to_server; \
http.uri; content:"union"; nocase; \
http.uri; content:"select"; nocase; distance:1; \
classtype:attempted-recon; sid:1000001; rev:1;)
The rule requires both `union` and `select` in the URI (case-insensitive, within 1 byte). This cuts false positives.
Rule 2: Detect SSH brute-force indicators (repeated auth failures in a short window are hard to judge from a single packet; this rule matches a malformed SSH version string):
alert tcp $EXTERNAL_NET any -> $HOME_NET 22 \
(msg:"ET LOCAL SSH - abnormal version string"; \
flow:established,to_server; \
content:"SSH-"; offset:0; \
content:"!"; \
classtype:attempted-admin; sid:1000002; rev:1;)
Rule 3: Detect common backdoor/malware C2 domains (DNS query hits a denylisted domain):
alert dns $HOME_NET any -> any any \
(msg:"ET LOCAL Malware - known C2 domain query"; \
dns.query; content:"malware-c2.example.com"; nocase; \
classtype:trojan-activity; sid:1000003; rev:1;)
A more practical approach is to put the C2 domain list in a `suricata-update`-managed ruleset, or use `dns.query; content:!;` with file-based rules (`datasets` can be configured outside `--rule`). This article keeps a single-rule form for clarity.
4. Enable local rules in the config
sudo nano /etc/suricata/suricata.yaml
Append under `rules-files:`:
rules-files:
- suricata.rules
- local.rules
5. Validate rule syntax and start
# Syntax and config check (-T test mode)
sudo suricata -T -c /etc/suricata/suricata.yaml \
-S /etc/suricata/rules/local.rules
# Start for real (systemd)
sudo systemctl enable --now suricata
# Check runtime status
sudo journalctl -u suricata -n 20 --no-pager
Note: capturing traffic needs packet-capture privileges. Default is `af-packet` or `pcap`. Set the NIC in `/etc/suricata/suricata.yaml` (`af-packet.interface`) to the real interface name.
6. Replay traffic to trigger and verify rules
# Manually trigger one SQL injection-pattern request
curl -s "http://example.com/index.php?id=1%20union%20select%201" > /dev/null
# Read alerts (JSON log)
sudo tail -n 5 /var/log/suricata/eve.json | jq -r \
'select(.alert) | .timestamp, .alert.signature, .alert.signature_id'
Expected output includes `ET LOCAL SQLi - union select in URI` and sid `1000001`.
Config Verification
# Ruleset stats (confirm local.rules is loaded)
sudo suricata -c /etc/suricata/suricata.yaml -T 2>&1 | grep -i "local"
# Should show a local.rules line, no syntax errors
# Count loaded rules
sudo suricata --dump-rule-counts -c /etc/suricata/suricata.yaml 2>/dev/null | head
FAQ
FAQ 1: sid conflict or duplicate?
Rule IDs must be globally unique; collisions cause load failure. Official rulesets use the 1-1000000 range; custom rules should use IDs above 1000000. Track multiple rules via `suricata-update list-sources` or `# sid` comments at the top of the rules file. If startup logs show `duplicate sid`, find the file, remove the duplicate, and reload.
FAQ 2: Too many alerts (high false positives)?
Tune in this order: 1) Tighten match conditions—combine multiple `content` matches with `nocase`, `distance`, `within` to pin location; 2) Limit protocol context (e.g. `http.uri` only, not full-payload match); 3) Use `threshold` for a time-window count (e.g. alert only if the same sid fires more than 5 times in 60 seconds); 4) After feeding `eve.json` into a SIEM, aggregate by source IP, promote high-confidence alerts, demote the rest to watch.
Summary
This article started from Suricata's three-part rule structure, wrote three working detection rules (SQL injection, abnormal SSH, malicious domains), and covered syntax check, load, and replay verification. Key points: custom sids start at 1000000; use application-layer keywords (`http.uri`/`dns.query`) for precision; run `suricata -T` before restarting; send alerts to eve.json for later analysis. Next step: feed Suricata alerts into ELK or an existing log platform for visualization and alert correlation.