homelab intermediate 18 min read

IDS/IPS with Suricata: Practical Home Network Protection

Learn how to deploy Suricata as an IDS/IPS on your home network, from installation and configuration to writing custom rules and tuning alerts for real-world threat detection.

E

Ed Wirsing

May 2, 2026

IDS/IPS with Suricata: Practical Home Network Protection

Why Suricata on a Home Network?

Most people think intrusion detection systems are overkill for a home network. I disagree. Between IoT devices phoning home to sketchy endpoints, smart TVs with questionable firmware update practices, and the ever-present risk of a compromised machine becoming part of a botnet, there’s plenty happening on a residential network that deserves scrutiny.

Suricata is my go-to for this. It’s open source, actively maintained by the Open Information Security Foundation (OISF), and it handles both IDS (passive monitoring) and IPS (active blocking) in a single engine. Unlike Snort — which is solid but has been moving toward a more commercial model — Suricata gives you multi-threaded performance out of the box, native support for protocol parsing via its built-in application layer detection, and a rule language that’s compatible with most existing Snort rulesets.

In this tutorial, we’ll set up Suricata on a dedicated Linux box (or VM) to monitor your home network traffic. We’ll start in IDS mode so you can see what’s happening without breaking anything, then move to IPS mode once you’re confident in your rule tuning.

Prerequisites

Before we start, you’ll need:

  • A Linux machine — Ubuntu 22.04/24.04 or Debian 12 are ideal. A Raspberry Pi 4 with 4GB+ RAM works, but expect performance limits above ~200 Mbps. An old laptop or a Proxmox VM is better.
  • Two network interfaces (for IPS mode) — one for management, one for the monitored network segment. For IDS mode, a single interface with port mirroring works fine.
  • A managed switch or router that supports port mirroring — most prosumer gear (Ubiquiti, MikroTik, even some TP-Link managed switches) can do this. If your router runs OpenWrt, you’re in great shape.
  • Root or sudo access on the monitoring machine.
  • Basic familiarity with Linux networking concepts — interfaces, bridges, iptables/nftables.

Network Architecture

There are two practical ways to position Suricata on a home network:

Option A: Passive IDS via Port Mirroring

Your router or managed switch mirrors all traffic to a dedicated monitoring port. Suricata listens on that port in read-only mode. This is the safest starting point — you can’t accidentally break connectivity.

[Internet] → [Router] → [Managed Switch] → [Devices]
                              |
                        (mirror port)
                              |
                     [Suricata IDS Box]

Option B: Inline IPS via Bridge

Suricata sits inline between your router and the rest of the network, inspecting (and potentially dropping) packets in real time. More powerful, but a misconfiguration means you lose connectivity.

[Internet] → [Router] → [Suricata IPS Box] → [Switch] → [Devices]
                          (eth0)    (eth1)

We’ll configure both, starting with Option A.

Installation

Let’s get Suricata installed. The OISF maintains an official PPA that tracks stable releases, which I strongly recommend over distro packages — they tend to lag behind significantly.

# Add the OISF stable PPA
sudo add-apt-repository ppa:oisf/suricata-stable
sudo apt update

# Install Suricata and dependencies
sudo apt install -y suricata suricata-update jq

# Verify installation
suricata --build-info | head -20

Check the version — you want 7.0+ for the best protocol detection and performance improvements. Confirm that AF_PACKET support is compiled in, which is critical for production capture:

suricata --build-info | grep -i "af_packet"

You should see AF_PACKET support: yes.

Initial Configuration

Suricata’s main config lives at /etc/suricata/suricata.yaml. It’s a large file, but we only need to touch a few sections to get started.

Define Your Home Network

This is the single most important configuration step. Suricata uses the HOME_NET variable to distinguish internal traffic from external traffic. Get this wrong and your alerts will be meaningless.

sudo cp /etc/suricata/suricata.yaml /etc/suricata/suricata.yaml.bak

Edit /etc/suricata/suricata.yaml and find the vars section near the top:

vars:
  address-groups:
    HOME_NET: "[192.168.1.0/24]"
    # If you have multiple subnets (VLANs, IoT network, etc):
    # HOME_NET: "[192.168.1.0/24, 192.168.10.0/24, 10.0.0.0/24]"
    EXTERNAL_NET: "!$HOME_NET"

  port-groups:
    HTTP_PORTS: "80"
    SHELLCODE_PORTS: "!80"
    SSH_PORTS: "22"
    DNS_PORTS: "53"

Warning: Don’t set HOME_NET to any. This will cause Suricata to treat every connection as both internal and external, which generates absurd numbers of false positives and defeats the purpose of directional rules.

Configure the Capture Interface

Find the af-packet section and set your monitoring interface:

af-packet:
  - interface: eth1
    cluster-id: 99
    cluster-type: cluster_flow
    defrag: yes
    use-mmap: yes
    tpacket-v3: yes
    ring-size: 2048
    block-size: 32768

If you’re running on modest hardware (Pi, old laptop), reduce the ring-size to 1024 to save memory. On anything with 8GB+ RAM, leave it at 2048 or bump it higher.

Set the Default Log Directory

default-log-dir: /var/log/suricata/

Make sure the directory exists and Suricata can write to it:

sudo mkdir -p /var/log/suricata
sudo chown suricata:suricata /var/log/suricata

Enable EVE JSON Logging

EVE (Extensible Event Format) is Suricata’s JSON log output, and it’s what makes Suricata genuinely useful. Every alert, flow, DNS query, TLS handshake, and HTTP request gets logged in structured JSON. This is what you’ll feed into your analysis tools.

outputs:
  - eve-log:
      enabled: yes
      filetype: regular
      filename: eve.json
      types:
        - alert:
            payload: yes
            payload-printable: yes
            packet: yes
            metadata: yes
        - http:
            extended: yes
        - dns:
            version: 2
        - tls:
            extended: yes
        - files:
            force-magic: yes
        - flow
        - stats:
            totals: yes
            threads: no

Rule Management with suricata-update

Suricata is only as good as its rules. The suricata-update tool handles fetching, merging, and managing rule sources.

# Fetch the default ET Open ruleset (free, community-maintained)
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 sslbl/ja3-fingerprints
sudo suricata-update enable-source etnetera/aggressive

# Update again to pull in the new sources
sudo suricata-update

The ET Open ruleset alone gives you solid coverage. The sslbl/ja3-fingerprints source is particularly valuable — it detects malware based on TLS client fingerprints (JA3 hashes), which is effective even against encrypted C2 traffic.

Check how many rules are loaded:

sudo suricata-update | tail -5

You should see something like Loaded 35000+ rules. Not all are enabled by default, and that’s fine — we’ll tune as we go.

Rule File Location

After suricata-update runs, the merged ruleset ends up at /var/lib/suricata/rules/suricata.rules. Make sure your suricata.yaml points to it:

default-rule-path: /var/lib/suricata/rules
rule-files:
  - suricata.rules

First Run — IDS Mode

Let’s validate the config and do a test run:

# Test configuration
sudo suricata -T -c /etc/suricata/suricata.yaml

# If no errors, start Suricata in IDS mode
sudo systemctl enable suricata
sudo systemctl start suricata

# Check it's running
sudo systemctl status suricata

Give it 30 seconds to initialize (loading 35,000+ rules takes a moment), then check the logs:

# Check for startup errors
tail -50 /var/log/suricata/suricata.log

# Watch alerts in real time
tail -f /var/log/suricata/eve.json | jq 'select(.event_type == "alert")'

To generate a test alert, you can use Suricata’s built-in test rule. From another machine on your network:

curl http://testmynids.org/uid/index.html

This should trigger ET Open signature 2100498 (GPL ATTACK_RESPONSE id check returned root). If you see it in the EVE log, congratulations — your IDS is working.

Writing Custom Rules

The ET Open ruleset covers a lot, but custom rules are where Suricata really shines for home network monitoring. Here are some practical rules I run on my own network.

Create a local rules file:

sudo touch /var/lib/suricata/rules/local.rules

Add it to your suricata.yaml:

rule-files:
  - suricata.rules
  - local.rules

Detect IoT Devices Calling Home to Unexpected Destinations

# Alert on any device in the IoT VLAN reaching out to non-standard ports
alert tcp 192.168.10.0/24 any -> $EXTERNAL_NET [!80,!443,!53,!123] (msg:"LOCAL IoT device connection to unusual port"; flow:to_server,established; classtype:policy-violation; sid:1000001; rev:1;)

Detect DNS over HTTPS Bypass Attempts

Some malware and even consumer devices use DoH to bypass your local DNS filtering. If you run Pi-hole or AdGuard Home, this rule catches devices sneaking around it:

# Detect DNS-over-HTTPS to known public resolvers
alert tls $HOME_NET any -> any 443 (msg:"LOCAL Possible DNS-over-HTTPS to public resolver"; tls.sni; content:"dns.google"; sid:1000002; rev:1;)
alert tls $HOME_NET any -> any 443 (msg:"LOCAL Possible DNS-over-HTTPS to Cloudflare"; tls.sni; content:"cloudflare-dns.com"; sid:1000003; rev:1;)
alert tls $HOME_NET any -> any 443 (msg:"LOCAL Possible DNS-over-HTTPS to Quad9"; tls.sni; content:"dns.quad9.net"; sid:1000004; rev:1;)

Detect Outbound SSH from Unexpected Hosts

If only your workstation should be initiating SSH connections, alert on anything else:

# Alert on SSH from non-admin devices
alert ssh !192.168.1.100 any -> $EXTERNAL_NET 22 (msg:"LOCAL SSH connection from unauthorized host"; flow:to_server; sid:1000005; rev:1;)

Detect Potential Reverse Shells

# Detect common reverse shell patterns in cleartext traffic
alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"LOCAL Possible reverse shell - /bin/sh"; flow:established; content:"/bin/sh"; content:"-i"; within:10; classtype:trojan-activity; sid:1000006; rev:1;)

After adding rules, validate and reload:

sudo suricata -T -c /etc/suricata/suricata.yaml
sudo systemctl reload suricata

Tuning: Suppressing False Positives

You will get false positives. A lot of them, at first. Tuning is not optional — it’s the difference between a useful security tool and a noise generator.

The threshold and suppression config file is the right place for this. Create or edit /etc/suricata/threshold.config:

# Suppress ET POLICY GNU/Linux APT User-Agent on your package manager box
suppress gen_id 1, sig_id 2013504, track by_src, ip 192.168.1.50

# Suppress excessive DNS alerts from your Pi-hole
suppress gen_id 1, sig_id 2027863, track by_src, ip 192.168.1.53

# Rate-limit noisy but potentially useful signatures to 1 alert per 60 seconds
rate_filter gen_id 1, sig_id 2210044, track by_src, count 1, seconds 60, new_action alert

Tip: Never disable a rule globally because of one noisy host. Use suppress with IP tracking instead. That way, if the same signature fires from a different host, you’ll still see it.

Review the noisiest signatures periodically:

# Top 20 alerting signatures in the last 24 hours
cat /var/log/suricata/eve.json | \
  jq -r 'select(.event_type == "alert") | .alert.signature_id' | \
  sort | uniq -c | sort -rn | head -20

Moving to IPS Mode

Once you’ve run in IDS mode for a week or two and are confident your rules aren’t going to block legitimate traffic, you can move to inline IPS mode.

Set Up a Network Bridge

# Install bridge utilities
sudo apt install -y bridge-utils

# Create the bridge (assuming eth0 = router side, eth1 = LAN side)
sudo ip link add br0 type bridge
sudo ip link set eth0 master br0
sudo ip link set eth1 master br0
sudo ip link set br0 up
sudo ip link set eth0 up
sudo ip link set eth1 up

To make this persistent across reboots, add to /etc/network/interfaces or create a netplan config:

# /etc/netplan/01-bridge.yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: no
    eth1:
      dhcp4: no
  bridges:
    br0:
      interfaces: [eth0, eth1]
      dhcp4: yes

Switch Suricata to IPS Mode

In suricata.yaml, change the af-packet section:

af-packet:
  - interface: eth0
    cluster-id: 99
    cluster-type: cluster_flow
    defrag: yes
    use-mmap: yes
    tpacket-v3: yes
    copy-mode: ips
    copy-iface: eth1

  - interface: eth1
    cluster-id: 98
    cluster-type: cluster_flow
    defrag: yes
    use-mmap: yes
    tpacket-v3: yes
    copy-mode: ips
    copy-iface: eth0

The key setting is copy-mode: ips — this tells Suricata to forward packets between interfaces, dropping any that match a drop rule action.

Now change your blocking rules from alert to drop:

# Example: drop confirmed C2 traffic instead of just alerting
drop tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"LOCAL Drop known C2 callback"; flow:to_server,established; content:"malicious-c2-domain.com"; http.host; classtype:trojan-activity; sid:1000010; rev:1;)

Warning: Be conservative about what you set to drop. Start with high-confidence signatures only — known malware C2, exploit kit traffic, established threat intel indicators. Keep everything else as alert until you’re sure it won’t block your spouse’s video calls.

Restart Suricata:

sudo systemctl restart suricata

Monitoring and Analysis

Raw EVE JSON logs are powerful but hard to read at scale. Here are practical options for a homelab:

Quick CLI Analysis

I keep a few shell aliases handy:

# Add to ~/.bashrc
alias suricata-alerts='tail -f /var/log/suricata/eve.json | jq "select(.event_type==\"alert\") | {timestamp, src_ip: .src_ip, dest_ip: .dest_ip, signature: .alert.signature}"'
alias suricata-dns='tail -f /var/log/suricata/eve.json | jq "select(.event_type==\"dns\") | {timestamp, src_ip: .src_ip, query: .dns.rrname, type: .dns.rrtype}"'
alias suricata-tls='tail -f /var/log/suricata/eve.json | jq "select(.event_type==\"tls\") | {timestamp, src_ip: .src_ip, sni: .tls.sni, ja3_hash: .tls.ja3.hash}"'

Automated Daily Report

Here’s a script I run via cron every morning:

#!/usr/bin/env python3
"""Daily Suricata alert summary."""
import json
import sys
from collections import Counter
from datetime import datetime, timedelta

EVE_LOG = "/var/log/suricata/eve.json"
cutoff = datetime.utcnow() - timedelta(hours=24)

alerts = Counter()
sources = Counter()

with open(EVE_LOG) as f:
    for line in f:
        try:
            event = json.loads(line)
        except json.JSONDecodeError:
            continue
        if event.get("event_type") != "alert":
            continue

        ts = datetime.strptime(event["timestamp"][:19], "%Y-%m-%dT%H:%M:%S")
        if ts < cutoff:
            continue

        sig = event["alert"]["signature"]
        alerts[sig] += 1
        sources[event["src_ip"]] += 1

print(f"=== Suricata Daily Summary ({cutoff.date()} to {datetime.utcnow().date()}) ===\n")
print("Top 10 Signatures:")
for sig, count in alerts.most_common(10):
    print(f"  {count:>6}  {sig}")

print("\nTop 10 Source IPs:")
for ip, count in sources.most_common(10):
    print(f"  {count:>6}  {ip}")

print(f"\nTotal alerts: {sum(alerts.values())}")
chmod +x /opt/suricata-daily-report.py
# Add to crontab
(crontab -l; echo "0 7 * * * /opt/suricata-daily-report.py | mail -s 'Suricata Daily Report' [email protected]") | crontab -

Log Rotation

Suricata’s EVE log will grow quickly. Set up logrotate:

# /etc/logrotate.d/suricata
/var/log/suricata/*.log /var/log/suricata/*.json {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        systemctl reload suricata
    endscript
}

Performance Considerations

On a typical home network (1 Gbps, 10-30 devices), Suricata runs comfortably on a 4-core machine with 4GB RAM. Some tuning tips:

  • CPU affinity: Pin Suricata worker threads to specific cores to avoid cache thrashing. In suricata.yaml, under threading, set cpu-affinity for management and worker threads.
  • Rule profiling: Enable profiling.rules in the config to identify rules that consume disproportionate CPU. Disable or optimize the worst offenders.
  • Memory caps: Set stream.memcap and flow.memcap appropriately. For a homelab, 128mb for each is usually plenty.
stream:
  memcap: 128mb
  checksum-validation: no  # Your NIC probably offloads checksums

flow:
  memcap: 128mb
  hash-size: 65536

Keeping Rules Updated

Set up automatic rule updates — stale signatures are nearly useless:

# /etc/cron.d/suricata-update
15 3 * * * root suricata-update && systemctl reload suricata

This pulls fresh rules at 3:15 AM daily and reloads Suricata without dropping traffic (reload performs a live rule swap).

Wrapping Up

Running Suricata on your home network gives you visibility that most people never have. You’ll see which devices are chattiest, which ones are reaching out to unexpected destinations, and — occasionally — you’ll catch something genuinely malicious before it becomes a problem.

Here’s the progression I recommend:

  1. Week 1-2: Run in IDS mode. Watch. Learn what normal looks like on your network. Suppress the false positives.
  2. Week 3-4: Write custom rules for your specific environment. Alert on things that matter to your network.
  3. Month 2+: Move to IPS mode for high-confidence signatures. Keep everything else in alert mode.

For next steps, consider feeding your EVE logs into a proper SIEM. The ELK stack is the traditional choice, but for a homelab I’d look at Wazuh — it integrates natively with Suricata and gives you a web dashboard, alerting, and compliance reporting without the overhead of managing Elasticsearch yourself. If you want something lighter, Grafana with Loki can ingest EVE JSON and give you solid dashboards with minimal resource usage.

The key insight with any IDS/IPS is that deploying it is 20% of the work. Tuning it, maintaining it, and actually reading the alerts is the other 80%. Treat it like a garden — tend it regularly and it’ll reward you.

#suricata #ids #ips #network-security #homelab #intrusion-detection #threat-detection #linux