Suricata IDS/IPS: Host and Container Network Intrusion Detection
Problem
Firewall rules control whether traffic is permitted; they do not inspect the content of permitted traffic. An HTTP request to port 80 passes a firewall rule allowing port 80 regardless of whether it contains a SQL injection payload, a known exploit, or a command-and-control beacon pattern.
Intrusion detection fills this gap: deep packet inspection against a signature database catches threats that perimeter rules cannot. Suricata is the open-source standard — multi-threaded, maintained by the OISF, supports the Emerging Threats and Suricata rule sets, runs in both passive (IDS) and active blocking (IPS) modes.
Common deployment gaps:
- Detection is passive-only and unactioned. Suricata logs alerts to a file that nobody reads. Alerts are not connected to a SIEM, not correlated, and not fed into automated response.
- Default rules without tuning generate noise. Deploying Emerging Threats Open without tuning produces thousands of alerts per hour — false positives from legitimate internal traffic. Alert fatigue sets in and the system is disabled.
- No container network coverage. Suricata is deployed on the host but inspects only traffic leaving the host. East-west traffic between containers on the same node — container-to-container lateral movement — goes uninspected.
- IPS mode misconfigured. Suricata is deployed in inline IPS mode (
nfqueue) but rules withdropaction are applied to all traffic including internal services. Legitimate traffic is blocked; Suricata is reconfigured to IDS-only mode, eliminating blocking capability. - Rules not updated. The initial rule set is installed at deployment and never updated. New exploits and C2 patterns added to threat intelligence feeds go undetected.
Target systems: Suricata 7.0+ (multi-threaded, Rust-based detection engine improvements); Emerging Threats Open/Pro rule sets; AF_PACKET and NFQUEUE capture modes; Kubernetes node inspection via CNI plugin integration; EVE JSON output for Elasticsearch/Loki ingestion.
Threat Model
- Adversary 1 — Known exploit against exposed service: An attacker sends a known exploit (CVE-matched) against an exposed web service. The exploit pattern is in the Emerging Threats rule set. Without IDS, the request reaches the application.
- Adversary 2 — C2 beaconing from compromised host: Malware on a compromised host beacons to a C2 server using HTTP, DNS, or HTTPS. C2 communication patterns — beacon intervals, JA3 TLS fingerprints, known C2 domain patterns — are detectable in Suricata rules.
- Adversary 3 — Lateral movement over SMB/RDP: An attacker who has compromised one host attempts SMB brute force or pass-the-hash against adjacent hosts. SMB anomaly and authentication rules fire on this pattern.
- Adversary 4 — Data exfiltration via DNS tunnelling: An attacker tunnels data out via DNS queries (long TXT records, high-frequency subdomain lookups). DNS tunnelling detection rules in Suricata catch this pattern.
- Adversary 5 — Container-to-container exploit: A compromised container exploits another container on the same node over the local bridge interface. Host-level packet inspection misses this if Suricata only inspects the physical interface.
- Access level: Adversaries 1 and 2 operate externally. Adversaries 3, 4, and 5 are post-compromise lateral movement and exfiltration.
- Objective: Exploit services, establish persistence via C2, move laterally, exfiltrate data.
- Blast radius: Without IDS, all post-compromise activity proceeds undetected. With IPS mode, known exploit traffic is blocked before reaching the application.
Configuration
Step 1: Install and Basic Configuration
# Ubuntu 22.04+ — install from official PPA.
add-apt-repository ppa:oisf/suricata-stable
apt-get update
apt-get install suricata
# RHEL 9 / Rocky Linux.
dnf install epel-release
dnf install suricata
# Verify installation.
suricata --build-info | grep "Suricata version"
# /etc/suricata/suricata.yaml — core configuration.
vars:
# Define your internal network ranges.
address-groups:
HOME_NET: "[10.0.0.0/8,172.16.0.0/12,192.168.0.0/16]"
EXTERNAL_NET: "!$HOME_NET"
HTTP_SERVERS: "$HOME_NET"
SQL_SERVERS: "$HOME_NET"
DNS_SERVERS: "[10.0.0.53,10.0.0.54]"
# Capture interface.
af-packet:
- interface: eth0
threads: 4 # Match CPU cores.
cluster-id: 99
cluster-type: cluster_flow # Per-flow affinity for reassembly.
defrag: yes
use-mmap: yes
tpacket-v3: yes
# Logging.
outputs:
- eve-log:
enabled: yes
filetype: regular
filename: /var/log/suricata/eve.json
types:
- alert:
payload: yes # Include packet payload (base64).
payload-buffer-size: 4kb
metadata: yes
- http:
extended: yes # Full HTTP headers.
- dns:
query: yes
answer: yes
- tls:
extended: yes # JA3/JA3S fingerprints.
- flow
# Performance.
threading:
set-cpu-affinity: yes
cpu-affinity:
- management-cpu-set:
cpu: [0]
- worker-cpu-set:
cpu: ["1-3"] # Workers on dedicated cores.
# Memory.
stream:
memcap: 512mb
checksum-validation: yes
inline: no # IDS mode. Set yes for IPS (nfqueue).
Step 2: Rule Management with suricata-update
# Install suricata-update.
pip3 install suricata-update
# Update default rule sources.
suricata-update update-sources
suricata-update enable-source et/open # Emerging Threats Open (free).
# suricata-update enable-source et/pro # Emerging Threats Pro (paid; better coverage).
# Apply updates.
suricata-update
# Reload rules without restarting Suricata.
suricatasc -c reload-rules
# Automate daily updates.
cat > /etc/cron.daily/suricata-update << 'EOF'
#!/bin/bash
/usr/bin/suricata-update && /usr/bin/suricatasc -c reload-rules
EOF
chmod +x /etc/cron.daily/suricata-update
Rule tuning — disable noisy rules before they cause alert fatigue:
# /etc/suricata/disable.conf — rules to disable.
# Format: rule ID or GID:SID.
# Disable overly broad HTTP rules that fire on legitimate traffic.
2013504 # ET WEB_SERVER Possible SQL Injection Blind - Too broad for dev environments.
2027758 # ET HUNTING SUSPICIOUS Fixit or similar scanners used internally.
# Disable rules for services not in your environment.
# (If you don't use SMB internally, these are all false positives.)
# 2000419-2000430 # SMB rules.
# /etc/suricata/threshold.conf — suppress repetitive alerts.
# Suppress after 10 alerts from same source in 60 seconds.
suppress gen_id 1, sig_id 2010935, track by_src, ip 10.0.0.0/8
# Threshold: alert only once per hour from same src/dst pair.
threshold gen_id 1, sig_id 2001219, type both, track by_rule, count 1, seconds 3600
Step 3: IPS Mode with NFQUEUE
In IPS mode, Suricata receives packets from the kernel via NFQUEUE, drops or accepts them, and rules with drop action block traffic:
# /etc/suricata/suricata.yaml — IPS mode.
# Change capture mode from af-packet to nfqueue.
nfq:
mode: repeat # Re-inject to same queue after decision.
batchcount: 20
fail-open: yes # On Suricata crash, default to ACCEPT (fail-open).
stream:
inline: yes # Enable inline/IPS mode.
# iptables rules to redirect traffic through Suricata.
# Forward traffic to NFQUEUE.
iptables -I INPUT -j NFQUEUE --queue-num 0
iptables -I OUTPUT -j NFQUEUE --queue-num 0
iptables -I FORWARD -j NFQUEUE --queue-num 0
# Start Suricata with nfqueue.
suricata -c /etc/suricata/suricata.yaml -q 0
# Persist via systemd (suricata.service should use -q 0 flag).
systemctl restart suricata
For gradual IPS rollout — start with alert action rules, switch to drop selectively:
# Rules start as alert. Promote specific high-confidence rules to drop.
# /etc/suricata/modify.conf — change action for specific rules.
2008983 "alert" "drop" # SQL injection pattern — high confidence, drop.
2010935 "alert" "drop" # Known CVE exploit — high confidence, drop.
# Apply modifications.
suricata-update --modify-conf /etc/suricata/modify.conf
Step 4: Container Network Inspection
For Kubernetes, inspect traffic on the container bridge interface:
# /etc/suricata/suricata.yaml — add container bridge interface.
af-packet:
- interface: eth0 # Host external interface.
threads: 4
cluster-id: 99
cluster-type: cluster_flow
- interface: cni0 # Container bridge (flannel/kubenet default).
threads: 2
cluster-id: 98
cluster-type: cluster_flow
copy-mode: ips # IPS mode on container traffic.
# For Cilium/Calico with VXLAN:
- interface: vxlan.calico
threads: 2
cluster-id: 97
cluster-type: cluster_flow
For Kubernetes DaemonSet deployment:
# kubernetes/suricata-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: suricata
namespace: security
spec:
selector:
matchLabels:
app: suricata
template:
metadata:
labels:
app: suricata
spec:
hostNetwork: true # Required to inspect host interfaces.
hostPID: true
containers:
- name: suricata
image: jasonish/suricata:7.0
args: ["-c", "/etc/suricata/suricata.yaml", "--af-packet"]
securityContext:
capabilities:
add: ["NET_ADMIN", "SYS_NICE"] # Required for AF_PACKET.
volumeMounts:
- name: config
mountPath: /etc/suricata
- name: logs
mountPath: /var/log/suricata
- name: rules
mountPath: /var/lib/suricata/rules
volumes:
- name: config
configMap:
name: suricata-config
- name: logs
hostPath:
path: /var/log/suricata
- name: rules
hostPath:
path: /var/lib/suricata/rules
Step 5: EVE JSON Log Ingestion
Suricata’s EVE JSON output ships to Elasticsearch or Loki for analysis:
# /etc/filebeat/filebeat.yml — ship Suricata EVE to Elasticsearch.
filebeat.inputs:
- type: log
paths:
- /var/log/suricata/eve.json
json.keys_under_root: true
json.add_error_key: true
fields:
log_type: suricata
output.elasticsearch:
hosts: ["https://elasticsearch:9200"]
index: "suricata-%{+yyyy.MM.dd}"
ssl:
certificate_authorities: ["/etc/ssl/certs/es-ca.crt"]
For Grafana/Loki:
# /etc/promtail/config.yml
scrape_configs:
- job_name: suricata
static_configs:
- targets: [localhost]
labels:
job: suricata
host: __hostname__
__path__: /var/log/suricata/eve.json
pipeline_stages:
- json:
expressions:
event_type: event_type
alert_signature: alert.signature
alert_severity: alert.severity
src_ip: src_ip
dest_ip: dest_ip
- labels:
event_type:
alert_severity:
Step 6: Custom Rules for Environment-Specific Detection
Write rules for your environment’s specific threat patterns:
# /etc/suricata/rules/local.rules
# Detect access to Kubernetes API server from non-cluster IPs.
alert tcp !10.0.0.0/8 any -> $KUBERNETES_API 6443 (msg:"External access to Kubernetes API server"; classtype:policy-violation; sid:9000001; rev:1;)
# Detect base64-encoded commands in HTTP POST bodies (common in webshells).
alert http $EXTERNAL_NET any -> $HTTP_SERVERS any (msg:"Possible webshell base64 command"; flow:established,to_server; http.request_body; content:"base64_decode"; nocase; classtype:web-application-attack; sid:9000002; rev:1;)
# Detect DNS queries for known C2 domain patterns (long random subdomain).
alert dns any any -> any 53 (msg:"Possible DNS tunnelling - long subdomain"; dns.query; pcre:"/^[a-f0-9]{32,}\./i"; classtype:trojan-activity; sid:9000003; rev:1;)
# Detect crypto mining pool connections.
alert tcp $HOME_NET any -> any [3333,4444,5555,7777,9999,14444,45700] (msg:"Possible crypto mining pool connection"; classtype:policy-violation; sid:9000004; rev:1;)
# Detect kubectl exec from unexpected sources.
alert http any any -> $KUBERNETES_API 6443 (msg:"kubectl exec API call"; http.uri; content:"/exec?"; classtype:policy-violation; sid:9000005; rev:1;)
Step 7: Telemetry
suricata_alerts_total{signature, severity, src_ip, dest_ip} counter
suricata_drops_total{signature} counter
suricata_flow_bypass_total{} counter
suricata_capture_kernel_drops{interface} counter
suricata_decoder_pkts_total{interface} counter
suricata_uptime_seconds{} gauge
Alert on:
suricata_alerts_total{severity="1"}— severity 1 (critical) alerts require immediate investigation.suricata_capture_kernel_dropsnon-zero — Suricata cannot process packets fast enough; dropping traffic uninspected. Increase CPU allocation or ring buffer size.suricata_uptime_secondsresets — Suricata restarted unexpectedly; review for crashes. In IPS mode, a crash causes fail-open (all traffic passes).- High
suricata_alerts_totalfrom a singlesrc_ip— potential scanning or automated exploit; consider blocking at firewall level.
Expected Behaviour
| Signal | No IDS | Suricata IDS/IPS |
|---|---|---|
| Known CVE exploit sent to service | Request reaches application | Alert fired; in IPS mode, packet dropped |
| C2 beacon from compromised host | Undetected outbound connection | Signature match; alert on beacon pattern; C2 IP/domain blocked in IPS mode |
| DNS tunnelling exfiltration | Data leaves via DNS | DNS anomaly rule fires on long subdomain pattern |
| Container-to-container lateral movement | Uninspected on bridge interface | Bridge interface inspected; SMB/RDP anomaly rules fire |
| SQL injection in HTTP request | Reaches application | Alert on injection pattern; dropped in IPS mode |
Trade-offs
| Aspect | Benefit | Cost | Mitigation |
|---|---|---|---|
| IPS mode (inline) | Blocks known exploits in real time | Risk of false positive blocking legitimate traffic | Start in IDS; promote rules to drop incrementally; test on staging |
| Emerging Threats Pro | Broader coverage, faster updates, IP reputation | Cost (~$600/year) | Free ET Open covers the most critical patterns; evaluate Pro for high-risk environments |
| Container bridge inspection | East-west visibility | Additional CPU for bridge traffic | Profile traffic volume; use sampling if CPU-constrained |
| Custom local rules | Environment-specific detection | Maintenance burden; risk of false positives | Test rules on replay captures before production; review quarterly |
fail-open: yes in IPS |
Service continuity on Suricata crash | Traffic bypasses inspection during crash | Monitor uptime metric; use redundant Suricata instances |
Failure Modes
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| Kernel packet drops | Suricata misses packets; gaps in detection | suricata_capture_kernel_drops metric |
Increase ring-size in af-packet config; add CPU capacity |
| Rule update breaks detection | Suricata fails to reload rules after update | Reload error in suricata.log; zero alerts after reload | Validate rules with suricata --test-config; roll back to previous rule set |
| IPS false positive blocks service | Legitimate traffic dropped; application unreachable | Application error rate spikes; alert on traffic drop | Add suppress or threshold for the offending rule; or change action back to alert |
| EVE log disk fills | Log rotation fails; suricata stops writing alerts | Disk usage alert; missing EVE entries | Compress and archive logs; set limit in EVE output config; ship to remote storage |
| Suricata crash in IPS mode | All traffic passes uninspected (fail-open) | suricata_uptime_seconds resets; uptime monitoring |
Investigate core dump; restart service; page on-call |