Suricata IDS on Proxmox for Network Threat Detection
Deploy Suricata 7 on a Proxmox LXC container to monitor all bridge traffic in real time. Covers tc mirroring, ET Open rules, alert tuning, and log integration.
On this page
Running Suricata 7.x as a network intrusion detection system on Proxmox gives you full visibility into inter-VM traffic without modifying a single guest. By the end of this guide you will have a dedicated Suricata LXC container watching a mirrored copy of your bridge traffic, writing structured JSON alerts you can query in real time or ship to your existing log stack.
Key Takeaways
- Traffic mirroring: Use
tc mirredto clone packets fromvmbr0to a dummy interface with zero impact on VM throughput - Container type: A privileged LXC container on Debian 12 is the simplest path; Suricata needs
NET_RAWandNET_ADMINcapabilities - Free ruleset: Emerging Threats Open is updated daily and covers the vast majority of homelab threat scenarios at no cost
- Day-one noise: A fresh ET Open install fires 200 to 500 false positive alerts in the first 24 hours—budget time to tune
- JSON-native: Suricata's
eve.logis newline-delimited JSON, feeding directly into Loki, Elasticsearch, or ajqone-liner
What You Need Before Starting
This guide targets Proxmox VE 9.1 with a standard Linux bridge (vmbr0). Before starting you need:
- At least 2 GB of RAM to spare for the Suricata container
- The Debian 12 LXC template downloaded on your node
- Internet access from within the container to pull rulesets
Download the template if you do not have it:
pveam update && pveam download local debian-12-standard
If you have already configured VLANs following the guide to configuring VLANs on Proxmox with Linux bridges, the same tc mirroring approach works on VLAN-aware bridges—mirror each bridge independently and list all interfaces in suricata.yaml.
If you are already running CrowdSec for brute-force defense, Suricata fills a different layer. CrowdSec analyzes application logs; Suricata inspects raw packets. They complement each other rather than overlap.
How Traffic Mirroring Works on a Linux Bridge
Suricata needs raw packet access but must never sit in the forwarding path where it could drop traffic or add latency. The solution is a passive mirror: Linux tc clones every packet traversing vmbr0 to a dedicated dummy interface, and Suricata reads from that copy in promiscuous mode.
vmbr0 (bridge) ---> tc mirred clone ---> dummy0 ---> Suricata (read-only)
Create the mirror on the Proxmox host:
# Load the dummy kernel module
modprobe dummy
# Create and activate the mirror interface
ip link add dummy0 type dummy
ip link set dummy0 promisc on
ip link set dummy0 up
# Mirror ingress traffic on vmbr0
tc qdisc add dev vmbr0 handle ffff: ingress
tc filter add dev vmbr0 parent ffff: protocol all u32 match u8 0 0 \
action mirred egress mirror dev dummy0
# Mirror egress traffic on vmbr0
tc qdisc add dev vmbr0 handle 1: root prio
tc filter add dev vmbr0 parent 1: protocol all u32 match u8 0 0 \
action mirred egress mirror dev dummy0
Make the mirroring survive reboots by adding post-up hooks to /etc/network/interfaces:
auto vmbr0
iface vmbr0 inet static
address 192.168.1.10/24
gateway 192.168.1.1
bridge-ports enp3s0
bridge-stp off
bridge-fd 0
post-up modprobe dummy
post-up ip link add dummy0 type dummy || true
post-up ip link set dummy0 promisc on
post-up ip link set dummy0 up
post-up tc qdisc add dev vmbr0 handle ffff: ingress
post-up tc filter add dev vmbr0 parent ffff: protocol all u32 match u8 0 0 action mirred egress mirror dev dummy0
post-up tc qdisc add dev vmbr0 handle 1: root prio
post-up tc filter add dev vmbr0 parent 1: protocol all u32 match u8 0 0 action mirred egress mirror dev dummy0
Gotcha: If you already have a root qdisc on vmbr0 from a previous QoS setup, the handle 1: root prio line fails with RTNETLINK answers: File exists. Check first with tc qdisc show dev vmbr0 and choose a different handle number.
Installing Suricata in a Proxmox LXC Container
Create a privileged container with 2 GB RAM. Suricata 7 with ET Open loaded idles at around 800 MB, so 2 GB gives comfortable headroom for traffic spikes.
pct create 200 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname suricata-ids \
--memory 2048 \
--cores 2 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.1.200/24,gw=192.168.1.1 \
--privileged 1
Before starting the container, grant the capabilities Suricata requires and pass dummy0 into the container namespace:
echo 'lxc.cap.keep = net_bind_service net_raw net_admin sys_nice' >> /etc/pve/lxc/200.conf
cat >> /etc/pve/lxc/200.conf << 'EOF'
lxc.net.1.type = phys
lxc.net.1.link = dummy0
lxc.net.1.name = suricata0
lxc.net.1.flags = up
EOF
Start the container and enter it:
pct start 200 && pct enter 200
Inside the container, install Suricata 7 from the official OISF stable repository:
apt-get update && apt-get install -y curl gnupg
curl -fsSL https://www.openinfosecfoundation.org/download/suricata-stable-debian.gpg \
| gpg --dearmor -o /usr/share/keyrings/suricata.gpg
echo 'deb [signed-by=/usr/share/keyrings/suricata.gpg] https://ppa.launchpad.net/oisf/suricata-stable/ubuntu jammy main' \
> /etc/apt/sources.list.d/suricata.list
apt-get update && apt-get install -y suricata suricata-update
Verify:
suricata --build-info | grep 'Suricata version'
# Suricata version 7.0.x RELEASE
Configuring Suricata to Watch the Mirror Interface
Edit /etc/suricata/suricata.yaml inside the container. The two critical sections are af-packet capture and eve-log output:
# /etc/suricata/suricata.yaml (relevant excerpts)
af-packet:
- interface: suricata0
cluster-id: 99
cluster-type: cluster_flow
defrag: yes
use-mmap: yes
tpacket-v3: yes
vars:
address-groups:
HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"
EXTERNAL_NET: "!$HOME_NET"
outputs:
- eve-log:
enabled: yes
filetype: regular
filename: /var/log/suricata/eve.json
types:
- alert:
payload: yes
payload-printable: yes
- dns:
query: yes
answer: yes
- http:
extended: yes
- tls:
extended: yes
Setting HOME_NET correctly matters: rules classify traffic direction based on this value, and getting it wrong produces alerts pointing the wrong way.
Loading and Updating Emerging Threats Open Rules
suricata-update is the official rule manager and ships with Suricata. Run it to fetch ET Open:
suricata-update
The first run fetches roughly 40,000 rules and takes a minute or two on a typical connection. Set up a daily cron to keep rules fresh:
echo '0 3 * * * root /usr/bin/suricata-update && systemctl reload suricata' > /etc/cron.d/suricata-update
Enable and start Suricata:
systemctl enable --now suricata
systemctl status suricata
If Suricata fails to start, the most common cause is that suricata0 is not visible yet inside the container because the lxc.net.1 config changes require a container restart to take effect. Run ip link show inside the container to confirm the interface appears before debugging further.
Reading Eve JSON Alerts in Real Time
Suricata writes all events to /var/log/suricata/eve.json in newline-delimited JSON. Watch alerts as they arrive:
tail -f /var/log/suricata/eve.json | jq 'select(.event_type == "alert")'
A typical alert entry looks like this:
{
"timestamp": "2026-05-31T14:22:03.112345+0000",
"event_type": "alert",
"src_ip": "192.168.1.45",
"src_port": 49823,
"dest_ip": "185.220.101.47",
"dest_port": 443,
"proto": "TCP",
"alert": {
"action": "allowed",
"signature_id": 2027865,
"signature": "ET TOR Known Tor Exit Node Traffic",
"category": "Misc Attack",
"severity": 2
}
}
Look up any rule by its SID to understand what triggered it:
grep 'sid:2027865' /var/lib/suricata/rules/suricata.rules
Why Alert Volume Is High on Day One and How to Tune It
Expect 200 to 500 alerts in the first 24 hours on a typical homelab. Most will be legitimate-looking noise: DNS queries hitting CDN IPs that share address space with known scanners, TLS certificate fingerprints ET flagged years ago, or your NAS phoning home through a service that looks suspicious to a generic ruleset.
Identify the noisiest rule IDs first:
cat /var/log/suricata/eve.json \
| jq -r 'select(.event_type == "alert") | .alert.signature_id' \
| sort | uniq -c | sort -rn | head -10
For each SID you confirm is a false positive on your network, add it to a suppress list:
# /etc/suricata/disable.conf
2027865
2013028
2021001
Re-run the update with the suppress list:
suricata-update --disable-conf /etc/suricata/disable.conf
systemctl reload suricata
Do this tuning pass in the first week. A noisy IDS that fires constantly gets ignored—which is strictly worse than no IDS at all. The goal is a signal-to-noise ratio where each alert is worth reading.
Shipping Suricata Alerts to a Centralized Log Stack
The eve.json format is natively supported by Loki, Elasticsearch, Wazuh, and Graylog. For a quick Loki integration, install Promtail inside the container and point it at the log file:
# /etc/promtail/config.yaml (inside Suricata LXC)
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://192.168.1.50:3100/loki/api/v1/push
scrape_configs:
- job_name: suricata
static_configs:
- targets: [localhost]
labels:
job: suricata
host: proxmox-node-01
__path__: /var/log/suricata/eve.json
pipeline_stages:
- json:
expressions:
event_type: event_type
src_ip: src_ip
- labels:
event_type:
src_ip:
In Grafana's Explore view, filter to alerts with:
{job="suricata"} | json | event_type = "alert"
A bar chart of alerts by category over 24-hour windows is enough to spot behavioral changes at a glance. If you are running Cloudflare Tunnel on Proxmox for zero-trust remote access, pairing that with Suricata gives you coverage at both the perimeter and inside your bridge fabric.
Performance Impact and When This Setup Is Worth It
On a Proxmox node handling 500 Mbps of inter-VM traffic, Suricata 7 with 40,000 ET Open rules uses roughly 1 to 2 CPU cores and 800 MB RAM. On an Intel N100 mini PC that leaves plenty of headroom for typical homelab workloads.
If CPU pressure is an issue, configure Suricata to use multiple AF_PACKET worker threads pinned to specific cores:
# /etc/suricata/suricata.yaml
threading:
set-cpu-affinity: yes
cpu-affinity:
- management-cpu-set:
cpu: [ 0 ]
- worker-cpu-set:
cpu: [ "1-3" ]
mode: exclusive
threads: 3
This setup earns its resource cost for any node that exposes services to the internet or hosts multiple VMs that could be compromised and used as lateral movement footholds. For a completely air-gapped lab, the benefit is marginal and you can skip it.
Conclusion
You now have Suricata 7 passively watching your Proxmox bridge traffic, writing structured alerts to eve.json, and tuned to cut through the day-one false positive flood. The mirroring approach means no in-path latency and no risk of Suricata taking down your network if it restarts. The logical next step is pairing this with CrowdSec on your Proxmox cluster to act on Suricata-flagged source IPs at the firewall level, closing the loop from detection to automated response.