Network security monitoring always looked intimidating to me — until I actually sat down, set up Snort 3 from scratch on Ubuntu, wrote my own detection rules, and watched it flag every single nmap scan in real time. This post walks through everything: installation, configuration, custom rules, and simulating real attacks to prove it works.
If you have ever wondered how intrusion detection systems actually work under the hood, this is a solid hands-on way to find out.
What We Are Building
A working Snort 3 IDS running on Ubuntu inside a virtual machine, monitored by a Kali Linux attacker VM. Snort will sit on the network interface, watch all traffic, and alert when it detects:
- ICMP Ping Sweep
- XMAS Scan
- FIN Scan
- NULL Scan
- SYN Scan
- TCP Connect Scan
- UDP Scan
Both VMs are connected via Bridged Adapter in VirtualBox, so they share the same subnet.
Environment Setup
| Machine | OS | Role | IP (example) |
|---|---|---|---|
| Defender | Ubuntu 22.04 | Snort 3 IDS | 192.168.1.104 |
| Attacker | Kali Linux 2026.1 | nmap scan source | 192.168.1.106 |
Both adapters set to Bridged Adapter in VirtualBox settings so they can reach each other.
Step 1 — Update Ubuntu and Install Dependencies
Open a terminal on the Ubuntu VM and run:
sudo apt update
Then install all required build tools and libraries in one command:
sudo apt install -y build-essential \
libpcap-dev \
libpcre2-dev \
libnet1-dev \
zlib1g-dev \
luajit \
hwloc \
libdumbnet-dev \
bison \
flex \
liblzma-dev \
openssl \
libssl-dev \
pkg-config \
libhwloc-dev \
cmake \
cpputest \
libsqlite3-dev \
uuid-dev \
libcmocka-dev \
libnetfilter-queue-dev \
libmnl-dev \
autotools-dev \
libluajit-5.1-dev \
libunwind-dev \
git \
wget \
ethtool
Step 2 — Create a Working Directory
mkdir snort-source-files
cd snort-source-files
Step 3 — Install LibDAQ (Snort's Data Acquisition Library)
LibDAQ is what allows Snort to capture packets from the network interface.
git clone https://github.com/snort3/libdaq.git
cd libdaq
sudo ./bootstrap
sudo ./configure
sudo make
sudo make install
cd ..
Step 4 — Install Tcmalloc (Memory Optimization)
Tcmalloc is a memory allocator from Google that reduces fragmentation and speeds up Snort under load.
wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.10/gperftools-2.10.tar.gz
tar xzf gperftools-2.10.tar.gz
cd gperftools-2.10
sudo ./configure
sudo make
sudo make install
cd ..
Step 5 — Install Snort 3
Now the main event — clone and build Snort 3 from source:
git clone https://github.com/snort3/snort3.git
cd snort3
sudo ./configure_cmake.sh --prefix=/usr/local --enable-tcmalloc
cd build
sudo make
sudo make install
The sudo make step takes several minutes — this is normal. You will see progress climb from 0% to 100%.
After install, create a symlink and verify the version:
sudo ldconfig
sudo ln -s /usr/local/bin/snort /usr/sbin/snort
snort -V
Expected output:
-*> Snort++ <*-
Version 3.12.2.0
By Martin Roesch & The Snort Team
Step 6 — Configure the Network Interface
Find your interface name (mine was enp0s3):
ip a
Set it to promiscuous mode so Snort can capture all traffic:
sudo ip link set dev enp0s3 promisc on
sudo ethtool -K enp0s3 gro off lro off
Create a systemd service to persist this across reboots:
sudo nano /etc/systemd/system/snort3-nic.service
Paste this content:
[Unit]
Description=Set Snort3 NIC in promiscuous mode
[Service]
Type=oneshot
ExecStart=/sbin/ip link set dev enp0s3 promisc on
ExecStart=/sbin/ethtool -K enp0s3 gro off lro off
[Install]
WantedBy=multi-user.target
Enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now snort3-nic.service
sudo systemctl status snort3-nic.service
Step 7 — Create the Rules Directory and Configure snort.lua
sudo mkdir -p /usr/local/etc/rules/local-rules
cd /usr/local/etc/snort
sudo nano snort.lua
Make the following changes in snort.lua:
-- Set your network
HOME_NET = '192.168.0.0/24'
EXTERNAL_NET = '!$HOME_NET'
include 'snort_defaults.lua'
Scroll to the detection section and add the IPS block:
ips =
{
include = '/usr/local/etc/rules/local-rules/local.rules',
variables =
{
nets =
{
HOME_NET = HOME_NET,
EXTERNAL_NET = EXTERNAL_NET
}
}
}
Save and exit with Ctrl+X → Y → Enter.
Step 8 — Write the Detection Rules
sudo nano /usr/local/etc/rules/local-rules/local.rules
Add these seven rules:
alert icmp any any -> $HOME_NET any (msg:"NMAP Ping Sweep Scan"; dsize:0; sid:1000001; rev:1;)
alert tcp any any -> $HOME_NET 22 (msg:"NMAP XMAS Scan"; flags:FPU; sid:1000002; rev:1;)
alert tcp any any -> $HOME_NET 22 (msg:"NMAP FIN Scan"; flags:F; sid:1000003; rev:1;)
alert tcp any any -> $HOME_NET 22 (msg:"NMAP NULL Scan"; flags:0; sid:1000004; rev:1;)
alert tcp any any -> $HOME_NET 22 (msg:"NMAP SYN Scan"; flags:S; sid:1000005; rev:1;)
alert tcp any any -> $HOME_NET 22 (msg:"NMAP TCP Connect Scan"; sid:1000006; rev:1;)
alert udp any any -> $HOME_NET any (msg:"NMAP UDP Scan"; sid:1000007; rev:1;)
Step 9 — Validate the Configuration
Before running Snort live, test that the config file loads without errors:
snort -c /usr/local/etc/snort/snort.lua -T
You should see at the end:
Snort successfully validated the configuration (with 0 warnings).
o")~ Snort exiting
Step 10 — Start Snort in Alert Mode
sudo snort -c /usr/local/etc/snort/snort.lua -i enp0s3 -A alert_fast
Snort is now running and watching enp0s3 for any traffic that matches the rules.
Step 11 — Simulate Attacks from Kali Linux
On the Kali VM, run each scan against the Ubuntu IP:
1. Ping Sweep
nmap -sn 192.168.1.104
2. XMAS Scan
sudo nmap -sX 192.168.1.104
3. FIN Scan
sudo nmap -sF 192.168.1.104
4. NULL Scan
sudo nmap -sN 192.168.1.104
5. SYN Scan
sudo nmap -sS 192.168.1.104
6. TCP Connect Scan
nmap -sT 192.168.1.104
7. UDP Scan
sudo nmap -sU 192.168.1.104
How to Verify
Watch the Snort terminal on Ubuntu while each nmap command runs on Kali. You will see live alert lines like this:
05/14-18:13:07.004392 [**] [1:1000001:1] "NMAP Ping Sweep" [**] [Priority: 0] {ICMP} 192.168.1.106 -> 192.168.1.104
05/14-18:15:21.301976 [**] [1:1000002:1] "NMAP XMAS Scan" [**] [Priority: 0] {TCP} 192.168.1.106:34107 -> 192.168.1.104:22
05/14-18:16:03.900911 [**] [1:1000003:1] "NMAP FIN Scan" [**] [Priority: 0] {TCP} 192.168.1.106:41928 -> 192.168.1.104:22
05/14-18:16:53.770290 [**] [1:1000004:1] "NMAP NULL Scan" [**] [Priority: 0] {TCP} 192.168.1.106:52411 -> 192.168.1.104:22
05/14-18:17:26.976133 [**] [1:1000005:1] "NMAP SYN Scan" [**] [Priority: 0] {TCP} 192.168.1.106:40209 -> 192.168.1.104:22
05/14-18:15:21.301976 [**] [1:1000006:1] "NMAP TCP Connect Scan" [**] [Priority: 0] {TCP} 192.168.1.106:34107 -> 192.168.1.104:22
05/14-18:12:48.402112 [**] [1:1000007:1] "NMAP UDP Scan" [**] [Priority: 0] {UDP} 192.168.1.1:1900 -> 239.255.255.250:1900
Each attack type triggers its corresponding rule — that means the IDS is working exactly as expected.
What I Learned
Working through this end to end taught me several things that documentation alone does not convey:
Snort rules are just pattern matchers. At their core, each rule is a protocol + direction + match condition. Once you understand that structure, writing new rules feels natural.
Promiscuous mode matters. Without promisc on, Snort only sees traffic addressed directly to the host. Flipping that one flag is what makes it a proper network monitor.
Building from source builds understanding. Watching each dependency compile, seeing LibDAQ slot into place, and then seeing Snort start up — it makes the whole stack tangible rather than abstract.
nmap flag combinations map to Snort flags directly. The -sX XMAS scan sets FIN, PSH, and URG flags — which maps to flags:FPU in the rule. That connection between the attacker's tooling and the defender's signature clicked during this lab.
Alert mode is just the beginning. Snort can also run in inline IPS mode and actively drop packets. This setup is the foundation for that.
Common Mistakes
| Mistake | What Goes Wrong | Fix |
|---|---|---|
| Using Snort 2 instead of Snort 3 | Config file format is completely different | Always clone from github.com/snort3/snort3
|
Wrong HOME_NET subnet |
Rules never fire because traffic is not matched | Check your actual subnet with ip a and set it exactly |
| Not setting promiscuous mode | Snort misses traffic not destined for the host | sudo ip link set dev enp0s3 promisc on |
| Interface name mismatch in command | Snort starts but captures nothing | Confirm with ip a — may be ens33, eth0, or enp0s3
|
| Rules path typo in snort.lua | Snort validates but fires no alerts | Double-check the exact path including subdirectories |
Skipping sudo ldconfig after install |
Snort binary cannot find shared libraries | Always run sudo ldconfig before testing |
flags:0 for NULL scan |
May vary between Snort versions | Test with snort -c snort.lua -T and verify rule loads |
Conclusion
Snort 3 is not the simplest tool to set up from scratch, but going through the full build process is worth it. You end up with a real understanding of how each piece fits together — LibDAQ capturing packets, the rules engine pattern matching, and the alert output confirming what the attacker is doing.
The combination of Snort writing alerts in one window while nmap runs in another makes the whole thing feel live and real. Each scan lights up the terminal on the defender side, which is a genuinely satisfying moment.
If you set this up yourself and run into issues, drop a comment below — happy to help debug.





































