Suricata is an open source network threat detection engine from the Open Information Security Foundation (OISF). It inspects traffic in real time, matches it against signature rules, decodes application protocols such as HTTP, TLS and DNS, and writes structured events in its EVE JSON format. In this tutorial you will install Suricata on Ubuntu 24.04, run it as an intrusion detection system (IDS) on the server's own network interface, load the free ET Open ruleset, add a custom rule and read the alerts. An optional final step shows how to run it inline as an intrusion prevention system (IPS) that drops traffic.
Prerequisites
To follow this tutorial you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 2 CPU cores and 4 GB of RAM. The ET Open ruleset alone uses around 1 to 2 GB of memory once loaded.
- A non-root user with
sudoprivileges. - SSH access from a second machine, which you will use to generate test traffic.
Step 1 - Installing Suricata from the OISF PPA
Ubuntu's own suricata package lags behind upstream. OISF maintains a PPA with the current stable release, which is what the Suricata documentation recommends for Ubuntu. Add it and install Suricata together with jq, which you will use to read JSON logs:
sudo add-apt-repository ppa:oisf/suricata-stable
sudo apt update
sudo apt install suricata jq
The package includes suricata-update, the official rule manager, and a systemd unit. Check the installed version and build features:
suricata --build-info | head -n 5
This is Suricata version 8.0.x RELEASE
Features: NFQ PCAP_SET_BUFF AF_PACKET HAVE_PACKET_FANOUT LIBCAP_NG LIBNET1.1 HAVE_HTP_URI_NORMALIZE_HOOK PCRE_JIT HAVE_NSS HTTP2_DECOMPRESSION HAVE_LUA HAVE_JA3 HAVE_JA4 HAVE_LIBJANSSON TLS TLS_C11 MAGIC RUST POPCNT64
...
Make sure NFQ and AF_PACKET appear in the feature list. AF_PACKET is used for IDS capture and NFQ for the optional IPS mode.
Step 2 - Configuring the network and capture interface
Suricata needs to know which addresses belong to your network (HOME_NET) and which interface to listen on. Find the name of your public interface and its address:
ip -brief address show
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 203.0.113.25/24 2001:db8::25/64 fe80::1/64
In this example the interface is eth0 and the server address is 203.0.113.25. Replace them with your own values, highlighted below as your_interface and your_server_ip.
Open the main configuration file:
sudo nano /etc/suricata/suricata.yaml
Near the top, in the vars section, set HOME_NET to the addresses of the server. Leave the private ranges in place if the server also sits on a private network:
vars:
address-groups:
HOME_NET: "[your_server_ip/32,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16]"
EXTERNAL_NET: "!$HOME_NET"
Then find the af-packet section and change the first interface entry to your interface:
af-packet:
- interface: your_interface
cluster-id: 99
cluster-type: cluster_flow
defrag: yes
Finally, find the community-id option in the eve-log output and enable it. The Community ID is a standard flow hash that lets you correlate Suricata events with other tools such as Zeek:
community-id: true
Save and close the file. The systemd unit shipped with the package starts Suricata in AF_PACKET mode, so it will use the interface you just configured.
Step 3 - Downloading the ET Open ruleset
A fresh install has no detection rules. suricata-update downloads rulesets, merges them into a single file at /var/lib/suricata/rules/suricata.rules and applies your enable, disable and modify policies. With no sources configured it uses the free Emerging Threats Open (ET Open) ruleset:
sudo suricata-update
...
25/9/2026 -- 10:14:02 - <Info> -- Loaded 64103 rules.
25/9/2026 -- 10:14:03 - <Info> -- Disabled 14 rules.
25/9/2026 -- 10:14:03 - <Info> -- Enabled 0 rules.
25/9/2026 -- 10:14:03 - <Info> -- Writing rules to /var/lib/suricata/rules/suricata.rules: total: 64103; enabled: 47251; added: 64103; removed 0; modified: 0
25/9/2026 -- 10:14:05 - <Info> -- Testing with suricata -T.
25/9/2026 -- 10:14:28 - <Info> -- Done.
You can list other available sources, some free and some commercial, with:
sudo suricata-update update-sources
sudo suricata-update list-sources
To add one, run sudo suricata-update enable-source <name> followed by sudo suricata-update. For now ET Open is enough.
Step 4 - Adding a custom rule
Your own rules should live in a separate file so that suricata-update never overwrites them. Create a local rules file:
sudo mkdir -p /etc/suricata/rules
sudo nano /etc/suricata/rules/local.rules
Add a rule that raises an alert for every ICMP echo request (ping) sent to the server. It is noisy by design, which makes it a good first test:
alert icmp any any -> $HOME_NET any (msg:"LOCAL ICMP echo request to server"; itype:8; classtype:misc-activity; sid:1000001; rev:1;)
Use signature IDs from 1000000 upwards for local rules so they never collide with public rulesets.
Tell Suricata to load the file. Open suricata.yaml again, find the rule-files list and add the absolute path of your file below suricata.rules:
default-rule-path: /var/lib/suricata/rules
rule-files:
- suricata.rules
- /etc/suricata/rules/local.rules
Before starting the service, test the whole configuration. The -T flag loads the configuration and every rule, then exits:
sudo suricata -T -c /etc/suricata/suricata.yaml -v
...
Info: detect: 2 rule files processed. 47252 rules successfully loaded, 0 rules failed, 0
...
Notice: suricata: Configuration provided was successfully loaded. Exiting.
If a rule has a syntax error, the output names the file and line.
Step 5 - Starting Suricata and testing detection
Enable and start the service, then confirm it is running:
sudo systemctl enable suricata
sudo systemctl restart suricata
sudo systemctl status suricata
● suricata.service - Suricata IDS/IDP daemon
Loaded: loaded (/usr/lib/systemd/system/suricata.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-09-25 10:20:11 UTC; 5s ago
...
Loading tens of thousands of rules takes 20 to 60 seconds. Wait until the engine reports that it has started:
sudo tail -f /var/log/suricata/suricata.log
Notice: threads: Threads created -> W: 2 FM: 1 FR: 1 Engine started.
Press Ctrl+C to stop following the log.
Now trigger a signature from the ET Open set. The domain testmynids.org returns a page that looks like the output of the id command run as root, which matches rule 2100498:
curl http://testmynids.org/uid/index.html
Check the human-readable alert log:
sudo tail -n 3 /var/log/suricata/fast.log
09/25/2026-10:22:40.118102 [**] [1:2100498:7] GPL ATTACK_RESPONSE id check returned root [**] [Classification: Potentially Bad Traffic] [Priority: 2] {TCP} 18.66.97.12:80 -> 203.0.113.25:50916
To test your custom rule, ping the server from your second machine:
ping -c 3 your_server_ip
Three LOCAL ICMP echo request to server alerts should appear in fast.log. Once you have seen them, remove the rule or change alert to a comment with #, since it would otherwise fill your logs.
Step 6 - Reading EVE JSON events
fast.log is useful for a quick look, but the main output is /var/log/suricata/eve.json. It contains one JSON object per line for alerts and for protocol events (DNS, HTTP, TLS, flows and more), which makes it easy to ship to a SIEM such as Elasticsearch, OpenSearch or Wazuh.
Show the last alert with only the most useful fields:
sudo jq -c 'select(.event_type=="alert") | {timestamp, src_ip, dest_ip, proto, sig: .alert.signature, sid: .alert.signature_id}' /var/log/suricata/eve.json | tail -n 1
{"timestamp":"2026-09-25T10:22:40.118102+0000","src_ip":"18.66.97.12","dest_ip":"203.0.113.25","proto":"TCP","sig":"GPL ATTACK_RESPONSE id check returned root","sid":2100498}
Count which signatures fire most often. This is the first thing to look at when tuning false positives:
sudo jq -r 'select(.event_type=="alert") | .alert.signature' /var/log/suricata/eve.json | sort | uniq -c | sort -rn | head
List the source addresses that trigger the most alerts:
sudo jq -r 'select(.event_type=="alert") | .src_ip' /var/log/suricata/eve.json | sort | uniq -c | sort -rn | head
Step 7 - Tuning and updating rules automatically
When a rule produces false positives, disable it through suricata-update instead of editing the generated suricata.rules file, which is rewritten on every update. Create the disable policy file:
sudo nano /etc/suricata/disable.conf
Add one signature ID per line. You can also disable a whole group with a regular expression:
# False positive on our monitoring system
2013504
# Disable all rules about games
re:ET GAMES
Regenerate the rules and tell the running Suricata to reload them without a restart:
sudo suricata-update
sudo suricatasc -c reload-rules
{"message": "done", "return": "OK"}
ET Open is updated daily, so schedule the update. Create a systemd service that downloads the rules and reloads Suricata:
sudo nano /etc/systemd/system/suricata-update.service
[Unit]
Description=Update Suricata rules
After=network-online.target suricata.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/suricata-update --reload-command "/usr/bin/suricatasc -c reload-rules"
Create a timer that runs it once a day:
sudo nano /etc/systemd/system/suricata-update.timer
[Unit]
Description=Daily Suricata rule update
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
Enable the timer, run the service once by hand and check that it succeeded:
sudo systemctl daemon-reload
sudo systemctl enable --now suricata-update.timer
sudo systemctl start suricata-update.service
sudo journalctl -u suricata-update.service -n 5 --no-pager
... <Info> -- Running /usr/bin/suricatasc -c reload-rules.
... <Info> -- Done.
... systemd[1]: Finished suricata-update.service - Update Suricata rules.
Step 8 - (Optional) Running Suricata as an inline IPS
In IDS mode Suricata only sees a copy of the traffic. To block traffic it has to sit in the packet path. On a single host you do this with NFQUEUE: netfilter hands each packet to Suricata, which returns a verdict (accept or drop).
WarningIn IPS mode a mistake can cut your SSH session. Test on a server where you have console access, and keep the
--queue-bypassoption shown below so traffic keeps flowing if Suricata stops.
First, create a systemd override so the service runs in NFQUEUE mode on queue 0 instead of AF_PACKET. Look at the current command line:
systemctl cat suricata | grep ExecStart
ExecStart=/usr/bin/suricata -D --af-packet -c /etc/suricata/suricata.yaml --pidfile /run/suricata.pid
Open an override file:
sudo systemctl edit suricata
Add the following block. The empty ExecStart= clears the original command before the new one is set. Keep the same --pidfile path your unit uses:
[Service]
ExecStart=
ExecStart=/usr/bin/suricata -D -q 0 -c /etc/suricata/suricata.yaml --pidfile /run/suricata.pid
Rules only drop traffic if their action is drop. Change your local test rule in /etc/suricata/rules/local.rules to:
drop icmp any any -> $HOME_NET any (msg:"LOCAL drop ICMP echo request"; itype:8; classtype:misc-activity; sid:1000001; rev:2;)
Restart Suricata, then send inbound and outbound traffic to queue 0. The rules are inserted at the top of the chains and are not persistent, so a reboot removes them:
sudo systemctl daemon-reload
sudo systemctl restart suricata
sudo iptables -I INPUT -j NFQUEUE --queue-num 0 --queue-bypass
sudo iptables -I OUTPUT -j NFQUEUE --queue-num 0 --queue-bypass
Ping the server again from your second machine. The pings now time out, and fast.log shows the drop:
sudo grep 'LOCAL drop' /var/log/suricata/fast.log | tail -n 1
09/25/2026-10:41:07.552190 [Drop] [**] [1:1000001:2] LOCAL drop ICMP echo request [**] [Classification: Misc activity] [Priority: 3] {ICMP} 198.51.100.7:8 -> 203.0.113.25:0
Public rulesets ship as alert rules. To turn specific ET Open rules into drops, list their signature IDs or a matching regular expression in /etc/suricata/drop.conf and run sudo suricata-update again, the same way as disable.conf. Only convert rules you have watched in IDS mode for a while.
To go back to IDS mode, delete the two iptables rules with sudo iptables -D INPUT -j NFQUEUE --queue-num 0 --queue-bypass and the equivalent OUTPUT command, remove the override with sudo systemctl revert suricata and restart the service.
Troubleshooting
- No alerts at all. Check that the interface in the
af-packetsection matches the output ofip -brief address show, and thatsuricata.logshowsEngine started. Also confirmHOME_NETincludes the server address. suricatascfails with "Unable to connect to socket". The command socket is only created after the engine has started. Wait untilEngine startedappears insuricata.log.- Suricata is killed shortly after starting. The kernel's out-of-memory killer stopped it. Check with
sudo journalctl -k | grep -i oom, then add memory or disable rule categories you do not need indisable.conf. - Many
SURICATA STREAMalerts. They usually point to checksum offloading on virtual NICs. Setchecksum-validation: nounder thestreamsection ofsuricata.yamland restart.
Conclusion
You now have Suricata inspecting your server's traffic with the ET Open ruleset, a local rules file for your own signatures, JSON events you can query with jq, and a daily timer that keeps the rules current. From here you can forward eve.json to a central log platform, enable additional rule sources with suricata-update, and gradually move trusted signatures to drop if you run Suricata inline.
