You want IDS alerts on a Debian server, so you install Suricata from apt and the service comes up active (running). What systemctl status does not tell you is that the engine loaded 0 signatures, so it watches every packet and matches none of them until you download a ruleset and get it into the running daemon. The package matters too: Debian 13 main carries a Suricata branch upstream no longer supports.
This guide shows how to install Suricata on Debian 13 from trixie-backports, load the ET Open rules with suricata-update and reload them without a restart. It then trims LAN decoder noise, schedules rule updates that fail loudly, and ends with a real test alert. Every command below was run on Debian 13 (trixie) with Suricata 8.0.7 from trixie-backports in October 2026, on a 2 vCPU, 4 GB VM, and the outputs are copied from that box.
Prerequisites
Suricata runs here as a passive IDS on the host’s own NIC, so it sees traffic to and from this server. Memory is the number to plan for. With the full ET Open ruleset the main process sat at about 423 MB. A live rule reload builds the new detection engine before it drops the old one, so we sampled a peak of 763 MB during a reload. The daily update job adds its own peak of about 472 MB, the same on suricata-update 1.3.4 and 1.3.8. A 2 GB box is tight once those overlap, so we’d start at 4 GB. Treat that as a floor for following along, not a sizing for a busy sensor, where traffic volume and the ruleset drive the real figure. What you need before you start:
- Debian 13 (trixie), with sudo access
- 2 vCPU and 4 GB RAM or more
- The trixie-backports repository enabled
- Outbound HTTPS for suricata-update to fetch rules
- A second host on the LAN for the local rule test (optional)
Our cloud image had trixie-backports enabled already. If yours does not, the official backports instructions add it with one deb822 file. Create it:
sudo vim /etc/apt/sources.list.d/debian-backports.sources
Paste the stanza from the instructions page as it stands; it only adds the suite, and backports packages stay at priority 100 until you ask for them with -t:
Types: deb deb-src
URIs: http://deb.debian.org/debian
Suites: trixie-backports
Components: main
Enabled: yes
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Then refresh the package lists:
sudo apt update
We did not run this step ourselves, because the cloud image already had the suite, so treat it as the documented method rather than a lab capture.
If the server is brand new, work through the first steps after installing Debian 13 before you add an IDS to it. We only ran this on Debian 13; Debian 12 carries different package versions and none of it was retested there.
Pick Suricata 8 from trixie-backports
Install the Suricata 8 build from trixie-backports, not the Suricata 7 package in Debian 13 main. Upstream declared Suricata 7 end of life with the 7.0.17 release, and the trixie package is older still. The Debian security tracker for suricata lists 35 CVEs against the trixie package as vulnerable with no DSA and marked ignored, while the 8.0.7 builds in forky and sid are listed as fixed. For an IDS that parses hostile traffic all day, that settles it. Backports get no Debian security-team coverage, though; the backports FAQ says fixes are best effort by the backporter, so watch upstream Suricata releases and new trixie-backports uploads yourself.
Both versions show up in apt, but the default policy hides the better one because backports carries priority 100. Look at the full version table, because the short policy line only shows the candidate.
apt-cache policy suricata suricata-update
This output is from our box before the switch, with the 7.0 package still installed. The trixie-backports lines are the ones to look for, and if they are missing, backports is not enabled on your system:
suricata:
Installed: 1:7.0.10-1+deb13u4
Candidate: 1:7.0.10-1+deb13u4
Version table:
1:8.0.7-1~bpo13+1 100
100 mirror+file:/etc/apt/mirrors/debian.list trixie-backports/main amd64 Packages
*** 1:7.0.10-1+deb13u4 500
500 mirror+file:/etc/apt/mirrors/debian.list trixie/main amd64 Packages
100 /var/lib/dpkg/status
suricata-update:
Installed: 1.3.4-1+deb13u1
Candidate: 1.3.4-1+deb13u1
Version table:
1.3.8-1~bpo13+1 100
100 mirror+file:/etc/apt/mirrors/debian.list trixie-backports/main amd64 Packages
*** 1.3.4-1+deb13u1 500
500 mirror+file:/etc/apt/mirrors/debian.list trixie/main amd64 Packages
500 mirror+file:/etc/apt/mirrors/debian-security.list trixie-security/main amd64 Packages
100 /var/lib/dpkg/status
The Debian cloud image we used ships with trixie-backports already in its sources. A server installed from the netinst ISO may not have it; if the backports lines are missing above, add the suite as shown in the prerequisites.
Install Suricata on Debian 13
Install from backports with -t, which raises the backports priority for this one command. The package pulls in Hyperscan and a set of runtime libraries, and suricata-update comes in as a Recommends, not a hard dependency. That matters if your apt config skips recommends: --no-install-recommends would leave you without the rule updater, so name both packages in that case.
sudo apt install -t trixie-backports suricata
On our VM that was 47 new packages and 47.1 MB on disk. If you prefer to skip recommends, this is the form that still gets you suricata-update:
sudo apt install -t trixie-backports --no-install-recommends suricata suricata-update
Confirm the version that landed. If this prints a 7.0 version, the -t flag was missed or backports is not enabled.
suricata -V
The backports build reports itself as a release build of the 8.0 branch:
This is Suricata version 8.0.7 RELEASE
Already running the 7.0 package? The same -t trixie-backports install upgrades it in place. We ran it with dpkg’s --force-confold option so our edited suricata.yaml was kept, and the upgrade removed the obsolete /etc/init.d/suricata and /etc/default/suricata conffiles. Diff your config against the new defaults afterwards, because settings added in 8.0 only appear in the packaged file. You keep your HOME_NET and rule paths, but you also miss any new option until you merge it by hand.
Set the capture interface
The package runs Suricata in af-packet mode on eth0, and it starts the service the moment the install finishes. That default is right on Debian cloud images and wrong on most installer-built servers and bare metal, where the NIC is called ens18, enp1s0 or similar. If the name does not match, the service may look started or may fail, and either way the tests later in this guide find nothing, so check it first.
ip -br link | grep -v lo
Our VM has one live NIC, eth0. The second one is down and unused, so the default fits this box.
eth0 UP bc:24:11:fc:e8:0f <BROADCAST,MULTICAST,UP,LOWER_UP>
ens19 DOWN bc:24:11:11:f5:2d <BROADCAST,MULTICAST>
Now find the af-packet interface line in the config. The first - interface: entry is the af-packet one, at line 662 of the stock file:
sudo grep -nE "^ HOME_NET:|^ - interface: " /etc/suricata/suricata.yaml | head -6
The other eth0 entry further down belongs to a different capture mode, so leave it alone:
18: HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"
662: - interface: eth0
742: - interface: default
750: - interface: default
856: - interface: eth0
883: - interface: default
If your interface is not eth0, open the file and change that first af-packet entry to your interface name. We’ll set HOME_NET in the same file later:
sudo vim /etc/suricata/suricata.yaml
Only the name changes; the line keeps its two-space indent and the dash, with your interface in place of eth0:
- interface: eth0
Save, then restart Suricata so it attaches to the right NIC:
sudo systemctl restart suricata
On a cloud image where the NIC already is eth0, there’s nothing to change and no restart needed.
Check what the service is really doing
The 8.0 unit runs /usr/bin/suricata -D --af-packet -c /etc/suricata/suricata.yaml --pidfile /var/run/suricata/suricata.pid as Type=notify, so systemd only reports it started once the engine is ready. Check the state and the unit’s exec lines.
systemctl is-enabled suricata; systemctl is-active suricata; systemctl cat suricata | grep -E "^Exec|^Type"
Enabled and active, with a socket-based reload and stop. Note the pid file path moved to /var/run/suricata/ compared with the 7.0 package.
enabled
active
Type=notify
ExecStart=/usr/bin/suricata -D --af-packet -c /etc/suricata/suricata.yaml --pidfile /var/run/suricata/suricata.pid
ExecReload=/usr/bin/suricatasc -c reload-rules ; /usr/bin/kill -HUP $MAINPID
ExecStop=/usr/bin/suricatasc -c shutdown
That is exactly the state a monitoring check would call healthy, and it is not. Suricata writes most of its startup messages to /var/log/suricata/suricata.log rather than the journal, so read that file before you trust the unit status:
sudo grep -E "Warning|Error|signatures processed|Engine started|This is Suricata" /var/log/suricata/suricata.log | tail -10
Two warnings and a zero. The engine started and created its capture threads, and it is inspecting traffic with no signatures at all, so every packet passes without a match:
[4679 - Suricata-Main] 2026-10-21 08:06:07 Notice: suricata: This is Suricata version 8.0.7 RELEASE running in SYSTEM mode
[4680 - Suricata-Main] 2026-10-21 08:06:07 Warning: detect: No rule files match the pattern /var/lib/suricata/rules/suricata.rules
[4680 - Suricata-Main] 2026-10-21 08:06:07 Warning: detect: 1 rule files specified, but no rules were loaded!
[4680 - Suricata-Main] 2026-10-21 08:06:07 Info: detect: 0 signatures processed. 0 are IP-only rules, 0 are inspecting packet payload, 0 inspect application layer, 0 are decoder event only
[4680 - Suricata-Main] 2026-10-21 08:06:07 Notice: threads: Threads created -> W: 2 FM: 1 FR: 1 Engine started.
Nothing in that output is an error, which is why the unit never goes red. The two detect warnings are the ones that matter.
Warning: No rule files match the pattern /var/lib/suricata/rules/suricata.rules
This warning means suricata.yaml points at a rules file that does not exist yet. The default config sets default-rule-path: /var/lib/suricata/rules with one entry, suricata.rules, and nothing creates that file except suricata-update. The /etc/suricata/rules directory the package installs only holds protocol event files such as decoder-events.rules and dns-events.rules, so do not point the config there hoping for detection rules. Run suricata-update (next section) and the warning goes away.
Warning: 1 rule files specified, but no rules were loaded!
Same cause, seen from the other side: one file is listed under rule-files and zero signatures came out of it. Suricata treats this as a warning rather than an error, which is why the unit stays green. If you monitor the box only with systemctl is-active, add a check for 0 signatures processed in the log, because that line is the only sign the sensor is blind.
Download the ET Open rules with suricata-update
suricata-update fetches the free Emerging Threats Open ruleset by default, merges it into one file at /var/lib/suricata/rules/suricata.rules, and tests the result with suricata -T before it finishes. You do not need to enable a source first. Run it with time, because the same command will later run from a timer and you want to know how long it holds the CPU:
time sudo suricata-update 2>&1 | grep -E "Loaded|Disabled|Enabled|Writing rules|Testing|Done|Error|Warning" | tail -9
It loaded 69,191 rules and wrote 53,230 of them as enabled, since the rest ship commented out in ET Open. Most of the 26 seconds went to the config test between the Testing and Done lines, and a failed test leaves the previous rules in place:
21/10/2026 -- 08:06:29 - <Info> -- Done.
21/10/2026 -- 08:06:31 - <Info> -- Loaded 69191 rules.
21/10/2026 -- 08:06:31 - <Info> -- Disabled 16 rules.
21/10/2026 -- 08:06:31 - <Info> -- Enabled 0 rules.
21/10/2026 -- 08:06:31 - <Info> -- Enabled 136 rules for flowbit dependencies.
21/10/2026 -- 08:06:31 - <Info> -- Writing rules to /var/lib/suricata/rules/suricata.rules: total: 69191; enabled: 53230; added: 69191; removed 0; modified: 0
21/10/2026 -- 08:06:31 - <Info> -- Testing with suricata -T.
21/10/2026 -- 08:06:52 - <Info> -- Done.
real 0m26.503s
user 0m23.081s
sys 0m0.366s
The screenshot below puts the two states side by side on the same VM: the zero-rules warnings from the fresh install, then the suricata-update summary that fixes them on disk.

On disk is the key phrase. The daemon that has been running since the install still holds the empty ruleset, because suricata-update only writes files, and the next section proves it.
Reload the rules into the running daemon
suricata-update writes the rules file and exits; it does not signal the running Suricata. We demonstrated that on the lab box right after the update above: clear fast.log, send the test request from the last section of this guide, count the alert lines, and read the engine’s resident memory in KB. You do not need to repeat this, but it is the quickest way to see the gap for yourself:
sudo truncate -s0 /var/log/suricata/fast.log; curl -s -m 10 http://testmyids.com; sleep 4; echo fast_log_lines=$(sudo wc -l < /var/log/suricata/fast.log); ps -o rss= -C Suricata-Main
The trigger page came back and nothing was logged, with the engine at about 54 MB, the size of an empty ruleset:
uid=0(root) gid=0(root) groups=0(root)
fast_log_lines=0
53692
You have two ways to load the rules. sudo systemctl restart suricata works, but it stops capture while the engine rebuilds. The live reload goes through the unix socket instead, which is also what the unit’s own ExecReload line calls, so capture keeps running on the old ruleset while the new one compiles. We’d use the socket every time, and the timer later in this guide does the same. Keep a restart for changes to suricata.yaml itself, such as a new interface. The command below runs the reload, the log check and the same test request in one go.
time sudo suricatasc -c reload-rules; sudo grep -E "rule reload|signatures processed" /var/log/suricata/suricata.log | tail -3; curl -s -m 10 http://testmyids.com >/dev/null; sleep 4; sudo cat /var/log/suricata/fast.log; ps -o rss= -C Suricata-Main
The reload took 9 seconds and answered OK, the log shows 53,235 signatures, the GPL rule fired, and memory rose to about 423 MB. The Ethertype line mixed into the alerts is noise we deal with two sections down (output trimmed: three more Ethertype lines and the raw packet bytes removed):
{"message":"done","return":"OK"}
real 0m9.243s
user 0m0.001s
sys 0m0.006s
[4680 - Suricata-Main] 2026-10-21 08:07:01 Notice: detect: rule reload starting
[4680 - Suricata-Main] 2026-10-21 08:07:08 Info: detect: tenant id 0: 53235 signatures processed. 1207 are IP-only rules, 4530 are inspecting packet payload, 47262 inspect application layer, 110 are decoder event only
[4680 - Suricata-Main] 2026-10-21 08:07:10 Notice: detect: rule reload complete
10/21/2026-08:07:10.898265 [**] [1:2200121:1] SURICATA Ethertype unknown [**] [Classification: Generic Protocol Command Decode] [Priority: 3]
10/21/2026-08:07:10.913131 [**] [1:2100498:7] GPL ATTACK_RESPONSE id check returned root [**] [Classification: Potentially Bad Traffic] [Priority: 2] {TCP} 217.160.0.187:80 -> 10.0.1.50:35478
423132
Anything other than "return":"OK" usually means the socket is not there, which in turn means the daemon is not running. The 8.0 engine is also lighter and quicker than the 7.0 package we tested first on the same VM. The 7.0 reload took 19 seconds and left the engine at about 555 MB, against 9 seconds and 423 MB here. If you see 0 signatures processed after a reload, the rules file is missing or empty, so check the last lines suricata-update printed for a download error.
Set HOME_NET to your subnet
Many ET rules only fire on traffic going into or out of $HOME_NET, so it should describe the network you actually defend. The stock file defines EXTERNAL_NET as everything outside HOME_NET, so a sloppy HOME_NET skews both sides of those rules. The default, on line 18, covers every private range at once:
192.168.0.0/1610.0.0.0/8172.16.0.0/12
That works, but rules that care about direction get less precise. On a single server, narrow it to the subnet the box sits on, which is 10.0.1.0/24 for our eth0 at 10.0.1.50. If the sensor watches a mirror port for several VLANs, list each of them instead. Open the config.
sudo vim /etc/suricata/suricata.yaml
Under vars: and address-groups:, replace the HOME_NET value with your own subnet. Keep the four spaces of indentation, because YAML is strict about it:
HOME_NET: "[10.0.1.0/24]"
Then make sure the edit landed on the active line and not on one of the commented examples below it. The stock file carries four commented HOME_NET variants right under the real one, so it is easy to edit the wrong line and save:
sudo grep -n "^ HOME_NET:" /etc/suricata/suricata.yaml
You should get one line back:
18: HOME_NET: "[10.0.1.0/24]"
Test the config before any restart. A YAML mistake here would stop the service from starting, and the test catches it without touching the running engine:
sudo suricata -T -c /etc/suricata/suricata.yaml 2>&1 | tail -2
The last line is the one you need; a broken file prints the error and the line number instead:
i: suricata: This is Suricata version 8.0.7 RELEASE running in SYSTEM mode
i: suricata: Configuration provided was successfully loaded. Exiting.
We left the engine in IDS mode on af-packet, which is what the Debian unit runs. Inline IPS with NFQUEUE or af-packet bridging is a separate setup and was not tested for this guide.
Suppress the Ethertype unknown alerts
Suricata 8 flags link-layer frames it cannot decode, and on a normal office or homelab LAN those arrive constantly. Within seconds of loading the rules our fast.log filled with them, mixed in with the real alerts.
SURICATA Ethertype unknown
This is decoder rule 1:2200121, Priority 3. The frames behind it on our LAN went to destination MACs starting 01 80 C2, the reserved range for spanning tree and other bridge protocols, plus some broadcasts. They come from the switch gear, not from an attacker, and there is nothing in them a host IDS can act on. Count them over a minute to see the volume:
sudo truncate -s0 /var/log/suricata/fast.log; sleep 60; echo ethertype_unknown=$(sudo grep -c "Ethertype unknown" /var/log/suricata/fast.log)
Forty-one in 60 seconds on a quiet lab network. On a busy segment you would see far more, and they would drown a dashboard:
ethertype_unknown=41
Suppress the signature in the threshold file. The package ships /etc/suricata/threshold.config, but the threshold-file line in suricata.yaml is commented out by default (line 2348 in the stock file), so the file is ignored until you enable it. Append the suppress line first:
echo "suppress gen_id 1, sig_id 2200121" | sudo tee -a /etc/suricata/threshold.config
Then uncomment the setting and confirm it is active. Without this step the suppress line does nothing.
sudo sed -i "s|^# threshold-file: /etc/suricata/threshold.config|threshold-file: /etc/suricata/threshold.config|" /etc/suricata/suricata.yaml; sudo grep -n "^threshold-file" /etc/suricata/suricata.yaml
The line now has no leading #. The number shifts if lines were added above it, as the local rule step later in this guide does:
2348:threshold-file: /etc/suricata/threshold.config
The restart in the next section loads it, and the log then reports Threshold config parsed: 1 rule(s) found. In the 60 seconds after that restart we counted ethertype_unknown=0. Suppress only what you have confirmed is link-layer chatter; if the same alert shows up on a link that should carry nothing but IP, look at it before you hide it.
Add a local rule
Your own rules belong in a separate file, because suricata-update rewrites /var/lib/suricata/rules/suricata.rules on every run and would throw away anything you added there. This one alerts on ICMP echo requests into HOME_NET, which is easy to trigger from another machine. Create the file.
sudo vim /etc/suricata/rules/local.rules
Add the rule. Local rules should use a sid of 1000000 or higher so they never collide with ET Open, and the classtype gives the alert a category in eve.json instead of a null:
alert icmp any any -> $HOME_NET any (msg:"LOCAL ICMP echo request to HOME_NET"; itype:8; classtype:misc-activity; sid:1000001; rev:1;)
Add the file to rule-files with its absolute path. A bare filename there is looked up under /var/lib/suricata/rules, not /etc/suricata/rules. This sed adds it under the existing entry and prints the block back:
sudo sed -i "s|^ - suricata.rules$| - suricata.rules\n - /etc/suricata/rules/local.rules|" /etc/suricata/suricata.yaml; sudo sed -n "/^rule-files:/,/^$/p" /etc/suricata/suricata.yaml | head -3
Both files should now be listed. If only suricata.rules shows, the indentation in your file differs from the stock two spaces and the sed did not match, so add the line by hand in vim:
rule-files:
- suricata.rules
- /etc/suricata/rules/local.rules
Restart Suricata so the new rule file and the threshold file load, then read the log lines that prove it:
time sudo systemctl restart suricata; systemctl is-active suricata; sudo grep -E "rule files processed|Threshold config parsed|Engine started" /var/log/suricata/suricata.log | tail -3
Because the 8.0 unit is Type=notify, the restart blocks until the engine is ready, about 11 seconds here, and Engine started is already in the log when it returns. The 7.0 package returned in 1.5 seconds and then spent about 20 more seconds loading rules with capture stopped, so on that version a test sent straight after a restart can miss. Look for 2 rule files processed, 0 rules failed and the threshold line:
real 0m10.918s
user 0m0.009s
sys 0m0.000s
active
[5290 - Suricata-Main] 2026-10-21 08:11:17 Info: detect: 2 rule files processed. 53231 rules successfully loaded, 0 rules failed, 0 rules skipped
[5290 - Suricata-Main] 2026-10-21 08:11:17 Info: threshold-config: Threshold config parsed: 1 rule(s) found
[5290 - Suricata-Main] 2026-10-21 08:11:19 Notice: threads: Threads created -> W: 2 FM: 1 FR: 1 Engine started.
A syntax error in local.rules shows up there as a failed count above zero while the rest of the ruleset still loads, so that line is often the only place you’d notice it. To trigger the rule, ping the Suricata host from another machine in the subnet. We ran this on the second LAN host, 10.0.1.60:
ping -c2 10.0.1.50
Back on the Suricata box, query eve.json for the local sid. A minimal Debian image has no jq, and our first try at this failed with jq: command not found, so install it from the Debian repository:
sudo apt-get install -y jq
Then pull out the latest alert for sid 1000001.
sudo jq -c 'select(.event_type=="alert" and .alert.signature_id==1000001) | {timestamp, src_ip, dest_ip, sig: .alert.signature, category: .alert.category}' /var/log/suricata/eve.json | tail -1
The alert carries the source host and the Misc activity category from the classtype:
{"timestamp":"2026-10-21T08:11:30.489891+0000","src_ip":"10.0.1.60","dest_ip":"10.0.1.50","sig":"LOCAL ICMP echo request to HOME_NET","category":"Misc activity"}
If your SIEM sorts or routes alerts by category, give every local rule a classtype for the same reason. Without one, the alert lands with a null classification and falls through most filters.
Update the rules daily with a systemd timer
Neither Debian package ships a timer or cron job for rule updates. On our box dpkg -L suricata suricata-update listed no timer or cron file, and systemctl list-timers showed nothing for Suricata, so ET Open stays frozen at the day you installed unless you schedule it. Because suricata-update doesn’t reload the daemon, the job has to do both steps, and a systemd service plus timer also puts the run result in the journal, which cron does not. Create the service unit first:
sudo vim /etc/systemd/system/suricata-update.service
This oneshot unit runs the update quietly and then the same socket reload you used earlier. The --fail flag is the important part, and the end of this section shows why:
[Unit]
Description=Update Suricata rules and reload them
After=network-online.target suricata.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/suricata-update -q --fail
ExecStartPost=/usr/bin/suricatasc -c reload-rules
Now the timer that triggers it.
sudo vim /etc/systemd/system/suricata-update.timer
Daily, with up to an hour of random delay so a fleet of sensors doesn’t hit the rule server at the same minute. Each run cost us about 26 seconds of CPU plus a reload, so you can run it more often, but daily is a sane default. Persistent=true runs a missed update after the box was powered off:
[Unit]
Description=Daily Suricata rule update
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
Load the units, enable the timer and confirm the next run is scheduled. Only the timer gets enabled, since the timer starts the service:
sudo systemctl daemon-reload; sudo systemctl enable --now suricata-update.timer; systemctl list-timers --no-pager | grep suricata
The timer shows its next trigger and the service it activates. The time is random within the first hour after midnight, so yours will differ:
Thu 2026-10-22 00:44:56 UTC 16h Wed 2026-10-21 07:28:53 UTC - suricata-update.timer suricata-update.service
Do not wait until midnight to find out whether it works. Start the service by hand and read its result:
sudo systemctl start suricata-update.service; systemctl show suricata-update.service -p Result -p ExecMainStatus
Result=success means the update and the reload both ran. If you see exit-code instead, read the journal for the unit before you touch anything else:
Result=success
ExecMainStatus=0
Here is why --fail is in the unit. When suricata-update cannot reach the rule server, it logs Failed to fetch ... will use latest cached version, rebuilds from the cached download and exits 0. We tested that by pointing the ET hostname at 127.0.0.1 and adding --force, because suricata-update skips the fetch entirely within 15 minutes of the last download: the default run returned 0 and only --fail returned 1. Without the flag, a sensor that lost access to the rule server keeps reporting success every night while its rules age. With it, the same blocked download fails the job, the journal says so and the reload does not run:
Job for suricata-update.service failed because the control process exited with error code.
See "systemctl status suricata-update.service" and "journalctl -xeu suricata-update.service" for details.
rc=1
Result=exit-code
ExecMainStatus=1
Oct 21 08:12:59 deb01 suricata-update[5526]: 21/10/2026 -- 08:12:59 - <Error> -- Failed to fetch https://rules.emergingthreats.net/open/suricata-8.0.7/emerging.rules.tar.gz: <urlopen error [Errno 111] Connection refused>
Oct 21 08:12:59 deb01 systemd[1]: suricata-update.service: Main process exited, code=exited, status=1/FAILURE
Oct 21 08:12:59 deb01 systemd[1]: suricata-update.service: Failed with result 'exit-code'.
Oct 21 08:12:59 deb01 systemd[1]: Failed to start suricata-update.service - Update Suricata rules and reload them.
That output is our lab demonstration with the rule host blocked, not something to reproduce. Hook systemctl --failed or the unit’s result into whatever alerting you already run. The timer syntax is covered in more depth in our guide to systemd timers on Linux. For log growth you do not need to add anything, because Debian ships /etc/logrotate.d/suricata. It keeps 14 compressed rotations and sends the daemon a HUP after each one, for these files in /var/log/suricata:
eve.json, every event as JSONfast.log, one line per alertstats.log, engine counterssuricata.log, startup and rule loading
On our VM eve.json reached 467 KB in the first five minutes of light traffic, so watch disk space on a busy link and lower the rotate count if /var is small.
Fix truncated packet alerts on the Suricata 7 package
Skip this section on Suricata 8. It only applies if you’re stuck on the 7.0 package from Debian main on a KVM or Proxmox guest with a virtio NIC.
SURICATA AF-PACKET truncated packet
On the 7.0 package, this alert and SURICATA IPv4 truncated packet were the first entries in our fast.log. They come from hardware receive offload (rx-gro-hw) on the virtio NIC merging TCP segments into packets bigger than the link MTU, and one 12.7 MB HTTP download produced 128 of them. With rx-gro-hw off, the same download produced 0. On Suricata 8 we left rx-gro-hw on and four such downloads produced no truncated-packet alerts, so the fix is not needed there.
If you need the fix, make it survive reboots with a systemd drop-in that runs ethtool before Suricata starts. ethtool is a Priority optional package, so install it if it’s missing (our cloud image already had it):
sudo apt install ethtool
Create the drop-in directory.
sudo mkdir -p /etc/systemd/system/suricata.service.d
Then open the drop-in file:
sudo vim /etc/systemd/system/suricata.service.d/offload.conf
Put your interface name in place of eth0. The leading - tells systemd to ignore a failure of this command (see systemd.service), so a wrong interface name or a NIC without that feature can’t stop Suricata from starting:
[Service]
ExecStartPre=-/usr/sbin/ethtool -K eth0 rx-gro-hw off
Reload systemd and restart Suricata to apply it.
sudo systemctl daemon-reload; sudo systemctl restart suricata
The better fix is still the upgrade to Suricata 8 at the top of this guide, which removes the problem and the unpatched CVEs together.
Trigger a test alert to prove Suricata detects
An IDS you have not seen alert is an IDS you are trusting on faith. The standard check is to fetch a page whose body is the output of id run as root, which matches the old GPL rule 2100498 (“id check returned root”). Most guides, including the earlier version of this one, point you at testmynids.org for it, and that no longer works.
curl: Could not resolve host: testmynids.org
The testmynids.org domain is gone, so curl http://testmynids.org/uid/index.html fails on DNS and Suricata has nothing to match. Ask a public resolver to confirm it isn’t your own DNS:
dig testmynids.org @1.1.1.1 | grep -E "status:"
Cloudflare’s resolver answers NXDOMAIN, which means the name does not exist at all. It isn’t a firewall or resolver problem on your side, so stop debugging Suricata if this is the test you used.
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 48588
Use testmyids.com instead. It still resolves and returns the same root id string, and plain HTTP matters here, because Suricata can’t read the body of an HTTPS response without TLS decryption. Clear the log, send the request and read what was caught:
sudo truncate -s0 /var/log/suricata/fast.log; curl -s -m 10 http://testmyids.com >/dev/null; sleep 4; sudo cat /var/log/suricata/fast.log
A Priority 2 alert for sid 2100498 follows within seconds, with the server’s answer coming back in to our host on 10.0.1.50:
10/21/2026-08:14:04.984569 [**] [1:2100498:7] GPL ATTACK_RESPONSE id check returned root [**] [Classification: Potentially Bad Traffic] [Priority: 2] {TCP} 217.160.0.187:80 -> 10.0.1.50:43070
No alert usually comes down to one of four things. Check them in this order, because the first two are the most common right after an install:
- Ruleset not loaded: look for
0 signatures processed - Suricata capturing on the wrong interface
- Engine still starting: wait for
Engine started - Request never left the box
The interface case is the one that fools people on bare metal, since a service pointed at an eth0 that doesn’t exist on their box may look started, or fail.
You do not have to depend on a third-party site at all. The rule matches the response content, not the hostname, so any web server that returns that string will fire it. On another LAN host (10.0.1.60 here), create the page and serve it with Python’s built-in web server, which keeps running in the foreground until you press Ctrl+C:
mkdir -p ~/ids/uid && echo "uid=0(root) gid=0(root) groups=0(root)" > ~/ids/uid/index.html && cd ~/ids && python3 -m http.server 8080
Then fetch it from the Suricata box and show the last two alerts. We captured this during our first run on the 7.0 package; the same GPL rule fired on Suricata 8 in every test above:
curl -s http://10.0.1.60:8080/uid/index.html; sleep 4; sudo tail -2 /var/log/suricata/fast.log
The GPL rule fired, with an ET INFO alert for the Python server banner as a bonus. This version works with no internet access at all, which makes it the better test for an isolated lab:
uid=0(root) gid=0(root) groups=0(root)
10/21/2026-07:25:57.122564 [**] [1:2034636:2] ET INFO Python SimpleHTTP ServerBanner [**] [Classification: Misc activity] [Priority: 3] {TCP} 10.0.1.60:8080 -> 10.0.1.50:47222
10/21/2026-07:25:57.123766 [**] [1:2100498:7] GPL ATTACK_RESPONSE id check returned root [**] [Classification: Potentially Bad Traffic] [Priority: 2] {TCP} 10.0.1.60:8080 -> 10.0.1.50:47222
fast.log is for humans. The same alerts land in /var/log/suricata/eve.json as JSON with the HTTP context attached, and that’s the file you’d ship to a SIEM. With jq from the local rule step, pull out the two GPL alerts (also from the 7.0 run):
sudo jq -c 'select(.event_type=="alert" and .alert.signature_id==2100498) | {timestamp, src_ip, src_port, dest_ip, proto, sig: .alert.signature, sev: .alert.severity, host: .http.hostname, status: .http.status}' /var/log/suricata/eve.json | tail -2
Each record carries the hostname and HTTP status that a plain packet alert doesn’t, so you can tell the public test site from the LAN host without decoding IPs:
{"timestamp":"2026-10-21T07:25:51.451101+0000","src_ip":"217.160.0.187","src_port":80,"dest_ip":"10.0.1.50","proto":"TCP","sig":"GPL ATTACK_RESPONSE id check returned root","sev":2,"host":"testmyids.com","status":200}
{"timestamp":"2026-10-21T07:25:57.123766+0000","src_ip":"10.0.1.60","src_port":8080,"dest_ip":"10.0.1.50","proto":"TCP","sig":"GPL ATTACK_RESPONSE id check returned root","sev":2,"host":"10.0.1.60","status":200}
The last check is the one that matters a week from now: reboot and test again. After the reboot the service and timer were active, Suricata loaded both rule files and the threshold file, the engine held about 448 MB, and testmyids.com fired the alert again:

With that alert in hand, point eve.json at something that watches it. The Wazuh agent and server read Suricata’s EVE format natively, and the Elastic stack or Graylog work if you’d rather build the dashboards yourself. When an alert needs a closer look than a log line gives you, capture the same traffic with Wireshark.