A properly configured firewall blocks unauthorized connections. An intrusion detection system (IDS) tells you what is happening inside the connections your firewall allows. These are complementary tools, not alternatives. If your firewall allows HTTPS inbound to your web servers, it cannot inspect the content of those connections for SQL injection, command injection, or data exfiltration. That is where Suricata comes in.
This guide covers deploying Suricata as a network IDS on a dedicated sensor behind a pfSense firewall, tuning it for performance, writing custom rules, and shipping alerts to a SIEM.
Architecture: Span Port Deployment
The cleanest deployment puts Suricata on a dedicated sensor that receives a copy of all traffic via a mirror/span port on your switch. This is passive — Suricata sees everything but cannot block anything (IDS mode, not IPS).
Internet
|
[pfSense FW] --- Port 1 (WAN)
|
+--- Port 2 (LAN) --- [Internal Network]
|
+--- Port 3 (SPAN/Mirror of Port 2) --- [sensor01 - Suricata]
Configure your managed switch to mirror all traffic on the LAN port to the Suricata sensor port. On a Cisco switch:
interface GigabitEthernet0/3
description Suricata SPAN destination
switchport mode access
switchport access vlan 1
monitor session 1 source interface Gi0/2 both
monitor session 1 destination interface Gi0/3
On the Suricata sensor, the monitoring interface should have no IP address — it is a receive-only tap:
# /etc/network/interfaces (on sensor01)
auto eth1
iface eth1 inet manual
up ip link set eth1 up promisc on
down ip link set eth1 down
IDS vs IPS Mode
IDS mode (passive monitoring) is safer for initial deployment. Suricata inspects a copy of traffic and generates alerts without affecting live connections. You cannot accidentally break production by misconfiguring a rule.
IPS mode (inline) places Suricata in the traffic path. It can drop malicious packets in real time, but a misconfigured rule or an overloaded Suricata instance can cause outages.
Start with IDS. Move to IPS only after you have thoroughly tuned your ruleset and are confident in your false-positive rate.
Installation and Base Configuration
On Debian/Ubuntu:
sudo apt-get install suricata suricata-update
The primary configuration file is /etc/suricata/suricata.yaml. Key sections to configure:
Network Variables
Tell Suricata what your internal network looks like:
vars:
address-groups:
HOME_NET: "[10.0.70.0/24, 10.0.80.0/24, 172.16.0.0/16]"
EXTERNAL_NET: "!$HOME_NET"
HTTP_SERVERS: "[10.0.70.20, 10.0.70.21]"
DNS_SERVERS: "[10.0.70.2, 10.0.70.3]"
SMTP_SERVERS: "[10.0.70.25]"
port-groups:
HTTP_PORTS: "[80, 443, 8080, 8443]"
SSH_PORTS: "[22, 30022]"
DNS_PORTS: "[53]"
Capture Configuration
For high-throughput networks, use AF_PACKET with multiple threads:
af-packet:
- interface: eth1
threads: 4
cluster-id: 99
cluster-type: cluster_flow
defrag: yes
use-mmap: yes
ring-size: 200000
block-size: 262144
buffer-size: 67108864 # 64 MB
The cluster_flow type ensures all packets from the same flow go to the same thread, which is essential for stateful inspection. The ring-size and buffer-size parameters prevent packet drops under load.
Threading and CPU Affinity
On a multi-core sensor, pin Suricata threads to specific cores to avoid cache thrashing:
threading:
set-cpu-affinity: yes
cpu-affinity:
- management-cpu-set:
cpu: [0]
- receive-cpu-set:
cpu: [1, 2, 3, 4]
- worker-cpu-set:
cpu: [5, 6, 7, 8, 9, 10, 11]
Dedicate core 0 to management (logging, stats), cores 1-4 to packet capture, and the remaining cores to rule matching workers. Adjust based on your hardware.
Rule Management
Use suricata-update to fetch and manage rulesets:
# Fetch ET Open rules (free)
sudo suricata-update
# List available rule sources
sudo suricata-update list-sources
# Enable additional free sources
sudo suricata-update enable-source oisf/trafficid
sudo suricata-update enable-source etnetera/aggressive
# Update and reload
sudo suricata-update
sudo systemctl reload suricata
Writing Custom Rules
ET Open covers common threats, but you need custom rules for your specific environment. Suricata rules follow a structured syntax:
action protocol src_ip src_port -> dst_ip dst_port (rule_options)
Detect internal lateral movement via SMB:
alert tcp $HOME_NET any -> $HOME_NET 445 (msg:"INTERNAL Lateral movement - SMB connection between workstations"; flow:to_server,established; content:"|ff|SMB"; offset:0; depth:4; classtype:policy-violation; sid:1000001; rev:1;)
Detect DNS tunneling (unusually long DNS queries):
alert dns $HOME_NET any -> any 53 (msg:"SUSPICIOUS DNS query over 100 bytes - possible tunneling"; dns.query; content:"."; pcre:"/^[a-zA-Z0-9-.]{100,}/"; classtype:bad-unknown; sid:1000002; rev:1;)
Detect potential C2 beacon (periodic HTTP POST to uncommon port):
alert http $HOME_NET any -> $EXTERNAL_NET !$HTTP_PORTS (msg:"SUSPICIOUS HTTP POST to non-standard port - possible C2"; flow:to_server,established; http.method; content:"POST"; http.host; pcre:"/^d+.d+.d+.d+$/"; classtype:trojan-activity; sid:1000003; rev:1;)
Detect base64-encoded PowerShell in HTTP traffic:
alert http any any -> $HOME_NET $HTTP_PORTS (msg:"ATTACK Potential base64 encoded PowerShell payload"; flow:to_server,established; http.request_body; content:"powershell"; nocase; content:"-enc"; nocase; distance:0; within:20; classtype:attempted-admin; sid:1000004; rev:1;)
Place custom rules in /etc/suricata/rules/local.rules and reference them in suricata.yaml:
rule-files:
- suricata.rules # managed by suricata-update
- local.rules # your custom rules
EVE JSON Logging
Suricata’s EVE (Extensible Event Format) JSON log is the primary output for SIEM integration. Configure it in suricata.yaml:
outputs:
- eve-log:
enabled: yes
filetype: regular
filename: /var/log/suricata/eve.json
types:
- alert:
tagged-packets: yes
payload: yes
payload-printable: yes
http-body: yes
http-body-printable: yes
- http:
extended: yes
- dns:
query: yes
answer: yes
- tls:
extended: yes
- files:
force-magic: yes
force-hash: [md5, sha256]
- stats:
totals: yes
threads: no
deltas: yes
Shipping to SIEM
Wazuh Integration
If you run Wazuh, install the Wazuh agent on sensor01 and configure it to read Suricata’s EVE log:
<!-- /var/ossec/etc/ossec.conf on sensor01 -->
<ossec_config>
<localfile>
<log_format>json</log_format>
<location>/var/log/suricata/eve.json</location>
</localfile>
</ossec_config>
Wazuh has built-in decoders and rules for Suricata alerts. They appear in the Wazuh dashboard under the “Threat Detection” module.
Filebeat to Elasticsearch/OpenSearch
Alternatively, ship EVE JSON directly to your indexer:
# /etc/filebeat/filebeat.yml on sensor01
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/suricata/eve.json
json.keys_under_root: true
json.add_error_key: true
output.elasticsearch:
hosts: ["https://siem01.internal.example-corp.com:9200"]
ssl.certificate_authorities: ["/etc/ssl/internal/ca.crt"]
index: "suricata-%{+yyyy.MM.dd}"
False Positive Tuning
The initial deployment will generate noise. Tune aggressively:
Suppress List
Suppress specific signatures for specific sources/destinations:
# /etc/suricata/threshold.config
# Suppress ET SCAN rules for your vulnerability scanner
suppress gen_id 1, sig_id 2010935, track by_src, ip 10.0.80.50
# Suppress DNS query alerts for your internal DNS resolvers
suppress gen_id 1, sig_id 2027863, track by_src, ip 10.0.70.2
suppress gen_id 1, sig_id 2027863, track by_src, ip 10.0.70.3
Threshold Rules
Reduce alert volume for noisy-but-valid signatures:
# Only alert once per minute per source IP for port scans
threshold gen_id 1, sig_id 2010937, type both, track by_src, count 1, seconds 60
# Rate limit SSH brute force alerts
threshold gen_id 1, sig_id 2001219, type threshold, track by_src, count 10, seconds 60
Performance Monitoring
Watch for packet drops — they mean Suricata cannot keep up:
# Check Suricata stats
sudo suricatasc -c "dump-counters" | python3 -m json.tool | grep -E "capture.kernel_drops|decoder.pkts"
# Check interface drops
ethtool -S eth1 | grep -i drop
# Monitor CPU usage per thread
top -H -p $(pgrep suricata)
If you see persistent drops:
- Increase
ring-sizeandbuffer-sizein the AF_PACKET config - Add more worker threads (needs more CPU cores)
- Disable expensive protocol parsers you do not need (e.g., if you do not inspect SMB traffic, disable the SMB parser)
- Consider hardware: a dedicated IDS sensor benefits from high core count CPUs and 10 Gbps NICs with RSS (Receive Side Scaling)
# Disable parsers you don't need
app-layer:
protocols:
smb:
enabled: no
nfs:
enabled: no
tftp:
enabled: no
Suricata, properly deployed and tuned, gives you deep visibility into network activity that a firewall alone cannot provide. Start in IDS mode, tune your rules over two to four weeks, then decide whether inline IPS makes sense for your environment.
