Skip to main content
RunBook Academy

← All labs in OPNsense

Lab · advanced · ~90 min

Lab: Install Suricata in IDS mode and validate an alert

B · Nested virtualisationC · Simulation

Objectives

  • Install os-suricata on OPNsense and confirm the daemon is running
  • Configure Suricata in IDS (alert-only) mode on the LAN interface with a defined HOME_NET
  • Subscribe to a free ruleset (Emerging Threats Open) and confirm rules loaded
  • Generate a known-bad signature (a `test` rule with a specific payload) and watch Suricata alert on it
  • Read the EVE JSON alert log and confirm the rule fired with the right signature id and category

Prerequisites

This lab installs Suricata on OPNsense in alert-only (IDS) mode, loads the free Emerging Threats ruleset, and proves the pipeline end-to-end by triggering a recognisable alert from a custom rule. By the end you can read an EVE JSON alert and know whether the firewall is actually inspecting traffic, or just claiming to.

The lab deliberately uses IDS mode, not IPS. Blocking traffic in a lab is a quick way to lock yourself out of OPNsense. The principles are identical; only the action field of the rule changes between alert and drop.

Objective

By the end of this lab, you can:

  • Install the os-suricata plugin and confirm Suricata is running.
  • Configure Suricata in IDS mode on the LAN interface with the correct HOME_NET.
  • Subscribe to a free ruleset and confirm rules loaded.
  • Add a custom rule that fires on a payload you can produce on demand.
  • Read the EVE JSON alert output and identify the rule by signature id, source, destination, and category.

Requirements

You need:

  • An OPNsense instance with a LAN interface carrying real traffic.
  • A host on the LAN that can curl to the gateway or to any service — what it talks to matters less than that it talks.
  • Outbound HTTPS from OPNsense to pull rulesets.
  • Shell access on OPNsense.
  • jq on a host where you want to parse EVE JSON. OPNsense does not ship jq by default; copy the JSON out and parse it elsewhere if you prefer.

Tasks

Task 1: Install the plugin

System → Firmware → Plugins → os-suricata → Install. The plugin installs Suricata and the GUI management pages. Confirm:

# On OPNsense
suricata --build-info | head -3
service suricata status

suricata --build-info reports the compile options; the output should include the NFQUEUE/AF_PACKET capture methods. The service may show as not running — that is fine, the next task starts it.

Task 2: Configure HOME_NET and interfaces

Services → Intrusion Detection → Administration:

FieldValue
Enabledon
Save logs to/var/log/suricata
InterfacesLAN

Services → Intrusion Detection → Interfaces → + Add (LAN):

FieldValue
Enabledon
InterfaceLAN
DescriptionLAN IDS
HOME_NET10.10.10.0/24
EXTERNAL_NET!$HOME_NET
Default log levelNotice
InspectionIDS (alert-only)
Pattern matcherHyperscan (if available)

The choice IDS (alert-only) is the key setting. IPS mode would install an NFQUEUE hook that drops packets; the GUI warning that follows the IPS-mode toggle is not a politeness, it is a real risk of locking yourself out.

Confirm the configuration was written:

cat /var/suricata/suricata.yaml | grep -A2 'HOME_NET:'

The output must show HOME_NET: "[10.10.10.0/24]". If HOME_NET is empty or any, every alert loses context — you cannot tell which alerts are local-to-internal and which are inbound.

Task 3: Download the ruleset

Services → Intrusion Detection → Download Rules:

FieldValue
RulesetET Open (Emerging Threats Open)
Enableon

Click Download. The GUI pulls the ruleset from rules.emergingthreats.net and stores them under /usr/local/etc/suricata/rules/.

Confirm:

ls -l /usr/local/etc/suricata/rules/
ls /usr/local/etc/suricata/rules/ | wc -l
wc -l /usr/local/etc/suricata/rules/emerging-*.rules

ET Open ships roughly 50,000 rules in 30+ categories. The file size and line count are the visible signal that the download worked.

Task 4: Add a custom rule

A custom rule is the diagnostic every IDS lab needs. The rule below fires on any TCP packet with the literal string OPNSENSE-LAB-TEST-PAYLOAD anywhere in the payload:

Services → Intrusion Detection → Rules → + Add:

FieldValue
Actionalert
Protocoltcp
Sourceany
Destinationany
Source portany
Destination portany
Direction->
Descriptionopnsense-lab-canary
Payloadcontent:“OPNSENSE-LAB-TEST-PAYLOAD”; nocase;

Click Save then Apply. The GUI writes the rule to /usr/local/etc/suricata/rules/opnsense-lab-custom.rules.

Verify:

cat /usr/local/etc/suricata/rules/opnsense-lab-custom.rules

A Suricata rule reads as one line of options-separated keywords. The custom rule should look like:

alert tcp any any -> any any (msg:"opnsense-lab-canary"; content:"OPNSENSE-LAB-TEST-PAYLOAD"; nocase; sid:1000001; rev:1;)

The signature id (sid) must be unique. The OPNsense auto-sid generator assigns one starting from 1000001. Two rules with the same sid silently fail to load — Suricata logs a warning and drops the second.

Task 5: Start Suricata

Services → Intrusion Detection → Administration → Apply.

Confirm the daemon is up:

service suricata status
ps -axo pid,command | grep suricata
sockstat -l | grep -E 'suricata'

A running Suricata in IDS mode is not listening on any socket — it is capturing packets via AF_PACKET on the LAN interface. The proof of life is ps showing the suricata process running with the LAN interface listed in its arguments:

ps -axo pid,command | grep -E '/suricata'

If the process is running but the interface is missing, Suricata captured on no interface and is silently not seeing traffic. Stop and restart the service after any interface change.

Task 6: Generate the canary traffic

From a LAN host, generate an HTTP request with the canary string in the body. The simplest path is a request to OPNsense itself:

# On the LAN host
curl -X POST -d 'OPNSENSE-LAB-TEST-PAYLOAD' \
    http://10.10.10.1:80/anything

This may 404 — that is fine. The point is the request body crossed the LAN interface where Suricata is capturing.

If the LAN host has a web server already, you can also use:

# On the LAN host, hit any local service
curl -A "OPNSENSE-LAB-TEST-PAYLOAD" http://10.10.10.50/

Either path produces a TCP packet on the LAN interface with the literal OPNSENSE-LAB-TEST-PAYLOAD in the payload, which the custom rule matches.

Task 7: Read the EVE JSON alert

The EVE JSON log is the structured output every modern IDS ships. The file is /var/log/suricata/eve.json on OPNsense.

tail -f /var/log/suricata/eve.json | jq 'select(.alert)'

The output is one JSON object per alert:

{
  "timestamp": "2026-08-14T12:00:00.000000+0000",
  "flow_id": 1234567890,
  "in_iface": "em1",
  "event_type": "alert",
  "src_ip": "10.10.10.100",
  "src_port": 51234,
  "dest_ip": "10.10.10.1",
  "dest_port": 80,
  "proto": "TCP",
  "alert": {
    "action": "allowed",
    "gid": 1,
    "signature_id": 1000001,
    "rev": 1,
    "signature": "opnsense-lab-canary",
    "category": ""
  },
  "payload_printable": "OPNSENSE-LAB-TEST-PAYLOAD",
  "payload": "T1BTTlNFTlNFLUxBQi1URVNULVBBeUxPQUQ=",
  "tags": []
}

The four fields that prove the alert is yours:

  • signature_id: 1000001 — the sid of the rule you wrote.
  • signature: "opnsense-lab-canary" — the msg: string.
  • payload_printable shows the literal canary.
  • action: "allowed" because this is IDS mode — the alert fired but the packet was allowed through.

If the file is empty or the alert is missing, Suricata is not seeing your traffic. Check Services → Intrusion Detection → Administration → Log for the engine’s view.

Task 8: Confirm an ET Open rule fires

The custom rule proves the inspection pipeline is wired. Now prove the ruleset is loaded. Pick a benign, well-known rule from ET Open and verify it is in the running config:

grep -c '^alert' /usr/local/etc/suricata/rules/emerging-*.rules

A non-zero count is the visible signal. ET Open also ships a test sid — 2100498 is a known informational rule that fires on any IP packet:

grep 'sid:2100498' /usr/local/etc/suricata/rules/emerging*.rules

If the rule is not in the loaded ruleset, you have not enabled the right ET Open category in the GUI. Re-check Task 3.

Task 9: Validate HOME_NET scoping

A healthy HOME_NET definition keeps noise out of the alert log. Check what Suricata thinks HOME_NET is:

grep 'HOME_NET' /var/suricata/suricata.yaml
suricata --list-app-layer-protos | head

If HOME_NET is [10.10.10.0/24] and an alert fires on traffic that has nothing to do with 10.10.10.0/24 (e.g. a packet to/from 10.10.20.0/24, the IPsec lab’s other side), the rule that fired is one of the rules that intentionally match on EXTERNAL_NET — DNS amplification, scanner traffic. Those are expected. Anything that looks like internal-to-internal traffic on a different subnet is misconfiguration.

Task 10: Disable the canary and stop Suricata

When you have validated the pipeline, disable the canary rule to avoid noise in the next lab:

Services → Intrusion Detection → Rules → opnsense-lab-canary → toggle off → Apply.

Stop Suricata to free the AF_PACKET socket and free the CPU:

service suricata stop
ps -axo pid,command | grep suricata || echo "no suricata running"

Validation

  • service suricata status reports running before Task 10.
  • /var/log/suricata/eve.json contains an entry with signature_id: 1000001, signature: "opnsense-lab-canary", and payload_printable matching the canary string.
  • The custom rule is visible at /usr/local/etc/suricata/rules/opnsense-lab-custom.rules.
  • ET Open rules are loaded: wc -l on /usr/local/etc/suricata/rules/emerging-*.rules returns a non-zero count.
  • HOME_NET in suricata.yaml is [10.10.10.0/24].

Expected Result

Suricata running in IDS mode on the LAN interface, subscribed to ET Open, with a custom canary rule that fires on a payload the operator can produce at will. The EVE JSON log shows the alert with the expected signature id, the literal payload, and an action: "allowed" because IDS mode does not drop.

Troubleshooting

No alert appears. Check, in order: (1) Suricata process is running with the LAN interface listed (ps -axo command | grep suricata), (2) HOME_NET is set, (3) the custom rule is loaded (grep canary /var/log/suricata/suricata.log after a reload), (4) the canary string actually crossed the LAN interface (capture with tcpdump -i em1 port 80 on OPNsense), (5) the rule action is alert and not pass.

Alert fires but signature_id is wrong. A sid collision with an ET Open rule. Sids above 1,000,000 are reserved for local rules; if your sid overlaps with an ET rule, Suricata drops the local one. Change the local sid to something uniquely high like 10000042.

Engine fails to start with hyperscan error. The Hyperscan pattern matcher is faster but requires AVX2. On older CPUs, switch to Aho-Corasick under Services → Intrusion Detection → Interfaces → Pattern matcher.

Engine starts but the log shows failed to load rule file. A rule file has a syntax error. The log line tells you which file and which sid. Common causes: missing semicolon at the end of the rule, unescaped colon in msg:, or a stale sid reference in a references: field.

Performance is poor. Suricata in IDS mode on a 1 Gbps link needs ~2-4 cores. If packet drops appear in stats.log, reduce the number of enabled rules or move from IDS mode to AF_PACKET with one worker thread per core. The OPNsense GUI exposes this under Services → Intrusion Detection → Interfaces → Advanced.

Cleanup

Stop Suricata and remove the lab’s footprints.

# Stop the daemon
service suricata stop

# Disable the canary rule in the GUI before removing the file:
# Services → Intrusion Detection → Rules → opnsense-lab-canary → Delete

# Remove the canary file
rm -f /usr/local/etc/suricata/rules/opnsense-lab-custom.rules

# Disable ET Open if you do not want it after the lab
# Services → Intrusion Detection → Download Rules → ET Open → disable

# Confirm Suricata is gone
ps -axo pid,command | grep -E '/suricata' || echo "no suricata running"
service suricata status

# Truncate the alert log so the next lab starts clean
: > /var/log/suricata/eve.json

Do not leave the canary rule loaded. It will fire on every HTTP request with the literal string, including legitimate admin panels that mention “test” in their content.

What you learned

  • Suricata IDS mode inspects but does not drop. IPS mode drops via NFQUEUE. The action field in the alert changes but the rule format does not.
  • HOME_NET is the operator’s choice and changes how alerts are categorised. An empty HOME_NET makes every alert lose context.
  • A custom rule with a unique canary string is the smallest diagnostic that proves the inspection pipeline is wired end to end.
  • EVE JSON is the structured output. Read signature_id, signature, payload_printable, and action first; the rest is enrichment.

Deliverables

  • · Suricata running in IDS mode on the LAN interface
  • · Emerging Threats Open ruleset subscribed and rules loaded
  • · A custom rule that fires on a recognisable test payload
  • · An EVE JSON alert entry proving the rule fired

Verification status

Last reviewed
2026-08-14
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.