Swiss...
Building a Free, Open-Source Network Monitoring Stack for ISPs
From sFlow collection to full DDoS detection — without spending a dime on licenses.
May 22, 2026
by Ezequiel Pineda 30 min read
Network monitoring dashboard with flow analysis and DDoS detection

A complete, battle-tested guide to monitoring your hosting infrastructure with sFlow, nfsen, LibreNMS, and real-time DDoS detection — all using free, open-source tools.

Introduction

If you're running a hosting company or small ISP and need to monitor your network for DDoS attacks, traffic anomalies, bandwidth utilization, and device health — you've probably looked at commercial tools and winced at the price tags. nProbe wants thousands per year. ntopng Enterprise isn't cheap either. PRTG charges per sensor.

This guide documents our production monitoring stack — the same system we use to protect SwissLayer infrastructure. Every bug we've hit, every workaround we've found, and every configuration file — documented for the sysadmin who'd rather spend a weekend building than a budget buying.

What we run:

  • sFlow collection from Cisco Nexus switches
  • Flow analysis with nfsen/nfdump — top talkers, protocol breakdown, DDoS detection
  • SNMP monitoring with LibreNMS — bandwidth graphs, CPU, memory, temperature, alerts
  • Telegram + Email alerting for port down, traffic spikes, and SYN floods
  • Security hardening with fail2ban, UFW, and Apache basic auth

All running on a single Ubuntu 22.04 VM with 8GB RAM and 120GB disk.

Architecture Overview

┌─────────────────────┐       ┌──────────────────────────────┐
│   Cisco Nexus       │       │ Monitoring VM (Ubuntu 22.04) │
│   Switch            │       │ <monitoring-vm-ip>           │
│                     │       │                              │
│                     │ sFlow │   sflowtool (:6343)          │
│   Po1 (uplink) ─────┼──────>│        ↓ NetFlow v5          │
│   Po2 (uplink)      │  UDP  │   nfcapd (:2055)             │
│                     │       │        ↓ disk                │
│   Loopback1         │       │   nfsen (web :80)            │
│   <agent-ip>        │       │                              │
│                     │ SNMP  │   LibreNMS (web :8080)       │
│   Management VLAN   │<─────>│                              │
│   <mgmt-ip>         │  v2c  │   MySQL (:3306)              │
└─────────────────────┘       └──────────────────────────────┘

Why this architecture? The monitoring VM sits outside the main network (at a different provider) so it survives if your primary infrastructure goes down. sFlow packets travel over the public internet from the switch to the VM. SNMP polls go back the other way. If a DDoS takes down your uplinks, the last sFlow samples before the outage are already stored on the external VM — you can analyze what hit you even after the fact.

Part 1: sFlow Configuration on Cisco Nexus

Prerequisites

sFlow must be enabled as a feature on the Nexus:

show feature | include sflow

If not enabled:

configure terminal
feature sflow
end
Create a Dedicated Loopback for sFlow

Don't use an existing interface IP as the sFlow agent — create a dedicated loopback so the agent address is stable and identifiable:

configure terminal
interface loopback1
  description sFlow-Agent-Monitoring
  ip address <your-loopback-ip>/32
  no shutdown
end

Use an IP from your own announced prefix space. If you use an upstream's IP (like a point-to-point link), external hosts may not be able to route back to it.

Configure the sFlow Collector
configure terminal
sflow collector-ip <monitoring-vm-ip> vrf default
sflow agent-ip <your-loopback-ip>
end

Important: The Nexus 3000 series supports only one sFlow collector. If you configure a second, it replaces the first — it does not add alongside it.

The Missing Piece: Data Sources

This is where most guides fail. Enabling feature sflow and setting a collector is not enough. On the Nexus 3000 platform, you must explicitly define data sources:

configure terminal
sflow data-source interface port-channel1
sflow data-source interface port-channel2
end

Without this command, the Nexus sends counter samples but zero flow samples. You'll see datagrams being sent (show sflow statisticsSent Datagrams incrementing) but Total Samples stays at zero. This is the single most common misconfiguration.

To verify sampling is working:

show sflow statistics

You should see Total Samples and Processed Samples incrementing within seconds.

Verify and Save
show sflow
copy running-config startup-config

Expected output:

sflow sampling-rate        : 4096
sflow max-sampled-size     : 128
sflow counter-poll-interval: 20
sflow max-datagram-size    : 1400
sflow collector-ip         : <monitoring-vm-ip> , vrf : default
sflow collector-port       : 6343
sflow agent-ip             : <your-loopback-ip>
sflow data-source interface port-channel1
sflow data-source interface port-channel2

Sampling rate of 4096 is appropriate for 40G links. For 100G, consider 8192 or 16384.

Gotcha: Source IP of sFlow Packets

The agent-ip is embedded inside the sFlow header, but the source IP of the UDP packet is determined by the Nexus's routing table — typically whatever interface faces the default route. Your firewall rules on the monitoring VM must allow the actual source IP, not the agent IP.

Part 2: The Monitoring VM

Base Setup

Ubuntu 22.04 LTS, minimal install. First fix DNS resolution if resolv.conf is a broken symlink (common when systemd-resolved is disabled):

rm /etc/resolv.conf
echo -e "nameserver 1.1.1.1\nnameserver 8.8.8.8" > /etc/resolv.conf

Install basic tools:

apt-get install iputils-ping dnsutils build-essential git autoconf automake \
  libtool pkg-config flex bison librrd-dev libpcap-dev libbz2-dev rrdtool \
  apache2 libapache2-mod-php php php-cli php-curl php-gd php-gmp php-json \
  php-mbstring php-mysql php-snmp php-xml php-zip snmp snmpd -y
Why Not ntopng?

We initially tried ntopng Community Edition. Two problems:

  1. sFlow collector is broken in recent versions — it registers the sflow:// interface but binds to a random UDP port instead of the configured one. The log shows Waiting for flows at port 0 regardless of what you specify.
  2. ntopng requires nProbe for proper flow collection — the --help output reveals it only supports physical interfaces, ZMQ, and Kafka as inputs. The sflow:// and netflow:// prefixes are parsed but non-functional without the commercial nProbe collector.

nProbe's demo mode is limited to 300 seconds. After 5 minutes, it stops exporting flows and spams Failed export -1 to the console. At $2,374+ for a permanent license, this is not "free monitoring."

The Working Stack: sflowtool + nfdump + nfsen

This is the classic ISP flow monitoring stack. It's been running in production networks since the mid-2000s.

Install sflowtool

sflowtool is not in Ubuntu's package repos — build from source:

cd /tmp
git clone https://github.com/sflow/sflowtool.git
cd sflowtool
apt-get install autoconf automake libtool -y
./boot.sh
./configure
make
make install

sflowtool receives sFlow datagrams and converts them to NetFlow v5 for downstream collectors.

Install nfdump (Standard Build — NOT the Ubuntu Package)

Critical: Do not use the Ubuntu nfdump package. It ships the NSEL/NEL build designed for Cisco ASA. This version marks all standard sFlow/NetFlow records as INVALID Ignore and reports zero values for flow statistics. You'll see data in the files but nfsen will show empty graphs.

Build standard nfdump from source:

cd /tmp
git clone https://github.com/phaag/nfdump.git
cd nfdump
git checkout v1.6.25
./autogen.sh
./configure --enable-nfprofile
make
make install
ldconfig

The --enable-nfprofile flag is required for nfsen integration.

Verify:

/usr/local/bin/nfdump -V
# Should show: nfdump: Version: 1.6.25
# Should NOT show: NSEL-NEL
Install and Configure nfsen
cd /tmp
wget https://github.com/phaag/nfsen/archive/refs/heads/master.zip -O nfsen.zip
unzip nfsen.zip
cd nfsen-main

Create the configuration:

cat > etc/nfsen.conf << 'EOF'
$BASEDIR = "/opt/nfsen";
$BINDIR = "${BASEDIR}/bin";
$LIBEXECDIR = "${BASEDIR}/libexec";
$CONFDIR = "${BASEDIR}/etc";
$HTMLDIR = "/var/www/html/nfsen";
$DOCDIR = "${BASEDIR}/doc";
$VARDIR = "${BASEDIR}/var";
$PROFILESTATDIR = "${BASEDIR}/profiles-stat";
$PROFILEDATADIR = "/var/cache/nfdump";
$BACKEND_PLUGINDIR = "${BASEDIR}/plugins";
$FRONTEND_PLUGINDIR = "${HTMLDIR}/plugins";
$PREFIX = '/usr/local/bin';
$USER = 'www-data';
$WWWUSER = 'www-data';
$WWWGROUP = 'www-data';
$HTMLDIR = "/var/www/html/nfsen";
$BUFFLEN = 200000;
$SUBDIRLAYOUT = 1;
$ZIPcollected = "-z";
$ZIPprofiles = 1;
$PROFILERS = 2;
$DISKLIMIT = 98;
%MAIL = ();
@plugins = ();
%sources = (
  'switch' => { 'port' => '2055', 'col' => '#0000ff', 'type' => 'netflow' },
);
EOF

Key settings:

  • $PREFIX = '/usr/local/bin' — points to our compiled nfdump, not the broken Ubuntu package
  • $ZIPcollected = "-z" — must be the flag string, not a boolean. Setting 1 causes nfcapd to receive -z=lz4 which is invalid syntax for nfcapd 1.6.x

Install:

perl install.pl etc/nfsen.conf
The -G Flag Bug

After installation, you must patch NfProfile.pm. nfsen calls nfdump with -G none to disable geolookup, but this flag doesn't exist in standard nfdump 1.6.25 (it's NSEL-specific). Without this patch, nfsen processes data but writes zeros to the RRD:

sed -i 's/-I -G none/-I/' /opt/nfsen/libexec/NfProfile.pm

Warning: Every time you re-run install.pl, this file gets overwritten. Re-apply the patch after any nfsen reinstall.

Fix the nfdump Version Hint

nfsen stores metadata about the nfdump version. If it's not set, nfcapd won't start:

perl -MStorable -e '$h = Storable::retrieve("/opt/nfsen/profiles-stat/hints"); $h->{"nfdump"} = 6; Storable::lock_store($h, "/opt/nfsen/profiles-stat/hints");'
Create systemd Services

sflowtool:

cat > /etc/systemd/system/sflowtool.service << 'EOF'
[Unit]
Description=sFlow to NetFlow v5 converter
After=network.target

[Service]
ExecStart=/usr/local/bin/sflowtool -p 6343 -c 127.0.0.1 -d 2055
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now sflowtool

nfsen:

cat > /etc/systemd/system/nfsen.service << 'EOF'
[Unit]
Description=NfSen Flow Analysis
After=network.target apache2.service

[Service]
Type=forking
ExecStart=/opt/nfsen/bin/nfsen start
ExecStop=/opt/nfsen/bin/nfsen stop
PIDFile=/opt/nfsen/var/run/nfsend.pid
User=root

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable nfsen

Part 3: LibreNMS for SNMP Monitoring

Installation

LibreNMS requires PHP 8.2+. On Ubuntu 22.04:

add-apt-repository ppa:ondrej/php -y
apt-get update
apt-get install php8.2 php8.2-cli php8.2-curl php8.2-fpm php8.2-gd \
  php8.2-gmp php8.2-mbstring php8.2-mysql php8.2-snmp php8.2-xml \
  php8.2-zip libapache2-mod-php8.2 -y

update-alternatives --set php /usr/bin/php8.2
a2dismod php8.1
a2enmod php8.2

Clone and set up:

useradd librenms -d /opt/librenms -M -r -s "$(which bash)"
cd /opt
git clone https://github.com/librenms/librenms.git
chown -R librenms:librenms /opt/librenms
chmod 771 /opt/librenms
setfacl -d -m g::rwx /opt/librenms/rrd /opt/librenms/logs /opt/librenms/bootstrap/cache/ /opt/librenms/storage/
setfacl -R -m g::rwx /opt/librenms/rrd /opt/librenms/logs /opt/librenms/bootstrap/cache/ /opt/librenms/storage/
su - librenms -c '/opt/librenms/scripts/composer_wrapper.php install --no-dev'
Database Setup
mysql -u root -p -e "
CREATE DATABASE librenms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'librenms'@'localhost' IDENTIFIED BY 'YourPasswordHere';
GRANT ALL PRIVILEGES ON librenms.* TO 'librenms'@'localhost';
FLUSH PRIVILEGES;"

Configure .env, generate key, migrate:

cd /opt/librenms
cp .env.example .env
# Edit .env with your DB credentials
su - librenms -c 'php /opt/librenms/artisan key:generate'
su - librenms -c 'php /opt/librenms/artisan migrate --seed --force'
su - librenms -c 'php /opt/librenms/artisan user:add admin -r admin'
Apache Configuration

Run LibreNMS on port 8080 (port 80 is for nfsen):

cat > /etc/apache2/sites-available/librenms.conf << 'EOF'

  DocumentRoot /opt/librenms/html
  ServerName librenms
  AllowEncodedSlashes NoDecode
  
    Require all granted
    AllowOverride All
    Options FollowSymLinks MultiViews
  

EOF

echo "Listen 8080" >> /etc/apache2/ports.conf
a2enmod rewrite
a2ensite librenms
systemctl restart apache2
SNMP Configuration on the Network Device

Check existing SNMP config:

show running-config snmp

If SNMP communities have ACLs (they should), add your monitoring VM's IP to the relevant ACL. Use a read-only community (network-operator group) for monitoring:

configure terminal
ip access-list <your-acl-name>
  permit ip <monitoring-vm-ip>/32 any
  permit ip any <monitoring-vm-ip>/32
end
Adding Devices to LibreNMS

Gotcha: ICMP may not work to network devices if ACLs block it. Disable the ping requirement:

su - librenms -c 'php /opt/librenms/lnms config:set icmp_check false'

Discover and poll:

su - librenms -c 'php /opt/librenms/artisan device:add <device-ip> --v2c -c <community> --force'
su - librenms -c 'php /opt/librenms/artisan device:discover <device-ip>'
su - librenms -c 'php /opt/librenms/artisan device:poll <device-ip>'
Cron and Scheduler
cp /opt/librenms/dist/librenms.cron /etc/cron.d/librenms
cp /opt/librenms/dist/librenms-scheduler.service /opt/librenms/dist/librenms-scheduler.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now librenms-scheduler.timer

Part 4: Alerting

Telegram Bot Setup
  1. Message @BotFather on Telegram → /newbot → save the token
  2. Send a message to your new bot
  3. Get your chat ID from https://api.telegram.org/bot<TOKEN>/getUpdates

Create alert script:

cat > /opt/nfsen/bin/telegram-alert.sh << 'SCRIPTEOF'
#!/bin/bash
BOT_TOKEN="your-bot-token"
CHAT_ID="your-chat-id"
ALERT_NAME="$1"
ALERT_STATUS="$2"
TIMESTAMP=$(date)

MESSAGE="NfSen Alert
Alert: ${ALERT_NAME}
Status: ${ALERT_STATUS}
Time: ${TIMESTAMP}"

curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
  -d chat_id="${CHAT_ID}" \
  -d text="${MESSAGE}" > /dev/null
SCRIPTEOF

chmod +x /opt/nfsen/bin/telegram-alert.sh
LibreNMS Alerts

Add Telegram as an alert transport in LibreNMS → Alerts → Alert Transports. LibreNMS comes with pre-built alert rules for common scenarios — enable them all:

  • Device Down (SNMP unreachable)
  • Device rebooted
  • Port Down / Port status up/down
  • Port utilisation over threshold
  • Sensor over/under limit
  • State Sensor Critical
nfsen DDoS Alerts

Configure alerts in nfsen's Alerts tab:

  • Flow Flood: Total flows > 500,000 (absolute) — 3x normal traffic
  • Bytes Flood: Total bytes > threshold using 10-min average baseline
  • SYN Flood: flows/s > 10-min average + 3000, with filter flags S and not flags ARFPU
Cron-Based Telegram DDoS Monitor

For immediate Telegram notifications independent of nfsen's alert system:

cat > /opt/nfsen/bin/ddos-monitor.sh << 'SCRIPTEOF'
#!/bin/bash
BOT_TOKEN="your-bot-token"
CHAT_ID="your-chat-id"
NFDUMP="/usr/local/bin/nfdump"
DATADIR="/var/cache/nfdump/live/switch"
LATEST=$(find $DATADIR -name "nfcapd.*" -not -name "*.current*" | sort | tail -1)

if [ -z "$LATEST" ]; then exit 0; fi

STATS=$($NFDUMP -r "$LATEST" -I 2>/dev/null)
FLOWS=$(echo "$STATS" | grep "^Flows:" | awk '{print $2}')

FLOW_THRESHOLD=500000

if [ "$FLOWS" -gt "$FLOW_THRESHOLD" ] 2>/dev/null; then
  curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
    -d chat_id="${CHAT_ID}" \
    -d text="ALERT: Flow flood! ${FLOWS} flows in last 5 min" > /dev/null
fi
SCRIPTEOF

chmod +x /opt/nfsen/bin/ddos-monitor.sh
echo "*/5 * * * * root /opt/nfsen/bin/ddos-monitor.sh" > /etc/cron.d/ddos-monitor

Part 5: Security Hardening

UFW Firewall
ufw allow 22/tcp                                          # SSH
ufw allow 53/tcp                                          # DNS
ufw allow 53/udp                                          # DNS
ufw allow 80/tcp                                          # nfsen (with basic auth)
ufw allow 8080/tcp                                        # LibreNMS (with app auth)
ufw allow from <switch-source-ip> to any port 6343 proto udp  # sFlow
ufw enable
Apache Basic Auth for nfsen

nfsen has no built-in authentication:

apt-get install apache2-utils -y
htpasswd -c /etc/apache2/.htpasswd admin

cat > /etc/apache2/conf-available/nfsen-auth.conf << 'EOF'

  AuthType Basic
  AuthName "NfSen - Restricted"
  AuthUserFile /etc/apache2/.htpasswd
  Require valid-user

EOF

a2enconf nfsen-auth
systemctl reload apache2
fail2ban
apt-get install fail2ban -y

cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime  = 3600
findtime = 600
maxretry = 5

[apache-auth]
enabled  = true
port     = http,8080
logpath  = /var/log/apache2/error.log
maxretry = 3

[apache-badbots]
enabled  = true
port     = http,8080
logpath  = /var/log/apache2/access.log
maxretry = 2

[sshd]
enabled  = true
port     = ssh
backend  = systemd
maxretry = 5
EOF

systemctl enable --now fail2ban

Lessons Learned

  1. ntopng Community Edition's sFlow collector is broken in recent versions. Don't waste time on it unless you're buying nProbe.
  2. Ubuntu ships NSEL nfdump, designed for Cisco ASA. It silently marks all standard flows as INVALID and writes zeros for all statistics. Always compile standard nfdump from source.
  3. nfsen calls nfdump with -G none, a flag that only exists in the NSEL build. Without patching this out, nfsen processes data but writes zeros to the RRD database.
  4. The Nexus 3000 series requires explicit sflow data-source commands. Enabling the feature globally only sends counters, not flow samples.
  5. sFlow agent-ip ≠ packet source IP. The agent IP goes inside the sFlow header; the UDP packet's source IP depends on routing. Firewall rules must match the actual source.
  6. RRD file permissions matter. nfsen runs as www-data. If you manually create an RRD as root, nfsen can't write to it — you get zero values with no error in the logs.
  7. Test SNMP from the monitoring VM, not from the switch. ACLs on trunk interfaces can block inbound SNMP. Use snmpwalk -v2c -c <community> <ip> .1.3.6.1.2.1.1.1.0 to verify.
  8. PHP timezone must match system timezone. LibreNMS validation will fail otherwise. Set date.timezone in ALL php.ini files (cli, apache2, fpm) and the system timezone with timedatectl.

Final Architecture

After all the troubleshooting, here's what actually runs in production:

Service Port Purpose
sflowtoolUDP 6343Receives sFlow, converts to NetFlow v5
nfcapdUDP 2055Stores flows to disk (managed by nfsen)
nfsenTCP 80Flow analysis web dashboard
LibreNMSTCP 8080SNMP monitoring web dashboard
MySQLTCP 3306Database for LibreNMS
fail2banBrute-force protection

All services are systemd-managed and survive reboots. Total disk footprint: ~4GB installed, ~500MB/day for flow data with 1:4096 sampling on 40G links.

Total cost: $0 in licenses. One VM. One weekend.

Conclusion

You don't need expensive commercial monitoring to protect your infrastructure. With the right combination of open-source tools — sflowtool, nfdump, nfsen, and LibreNMS — you get professional-grade network visibility: real-time flow analysis, historical traffic data, DDoS detection, and instant alerts to your phone.

The setup takes a weekend. The bugs documented here took us days to figure out. Now they won't take you any time at all.

If you're running a hosting company and want infrastructure that comes with this level of monitoring built in, check out our dedicated servers — or reach out if you need help setting up monitoring for your own network.