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.
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:
All running on a single Ubuntu 22.04 VM with 8GB RAM and 120GB disk.
┌─────────────────────┐ ┌──────────────────────────────┐
│ 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.
sFlow must be enabled as a feature on the Nexus:
show feature | include sflow
If not enabled:
configure terminal
feature sflow
end
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 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.
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 statistics → Sent 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.
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.
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.
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
We initially tried ntopng Community Edition. Two problems:
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.--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."
This is the classic ISP flow monitoring stack. It's been running in production networks since the mid-2000s.
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.
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
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.xInstall:
perl install.pl etc/nfsen.conf
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.
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");'
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
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'
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'
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
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
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>'
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
@BotFather on Telegram → /newbot → save the tokenhttps://api.telegram.org/bot<TOKEN>/getUpdatesCreate 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
Add Telegram as an alert transport in LibreNMS → Alerts → Alert Transports. LibreNMS comes with pre-built alert rules for common scenarios — enable them all:
Configure alerts in nfsen's Alerts tab:
flags S and not flags ARFPUFor 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
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
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
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
INVALID and writes zeros for all statistics. Always compile standard nfdump from source.-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.sflow data-source commands. Enabling the feature globally only sends counters, not flow samples.snmpwalk -v2c -c <community> <ip> .1.3.6.1.2.1.1.1.0 to verify.date.timezone in ALL php.ini files (cli, apache2, fpm) and the system timezone with timedatectl.After all the troubleshooting, here's what actually runs in production:
| Service | Port | Purpose |
|---|---|---|
| sflowtool | UDP 6343 | Receives sFlow, converts to NetFlow v5 |
| nfcapd | UDP 2055 | Stores flows to disk (managed by nfsen) |
| nfsen | TCP 80 | Flow analysis web dashboard |
| LibreNMS | TCP 8080 | SNMP monitoring web dashboard |
| MySQL | TCP 3306 | Database for LibreNMS |
| fail2ban | — | Brute-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.
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.