Skip to content

ntopng Traffic Analysis: Finding Bandwidth Hogs

ntopng Traffic Analysis: Finding Bandwidth Hogs Without Losing Your Mind

Section titled “ntopng Traffic Analysis: Finding Bandwidth Hogs Without Losing Your Mind”

It’s 3:14 AM. PagerDuty fires. The WAN link on your primary DC is pinned at 98% and has been for 40 minutes. You SSH into the edge router, run show interface GigabitEthernet0/0/0, and see… bytes. Lots of bytes. No idea who. You open a second terminal, run iftop, watch it scroll for ten minutes, and learn exactly nothing actionable. Meanwhile the on-call Slack channel fills with “is it the network?” from people who should know better.

This is the failure mode ntopng exists to eliminate. Flow-based analysis tells you who is talking, to whom, over what protocol, and for how long — in seconds, not after a packet capture marathon. Packet capture has its place (deep forensics, payload inspection, protocol debugging), but for the question “what is eating my bandwidth right now,” flows win every time.

This guide is for sysadmins, NetOps, and SecOps engineers who already know their way around a terminal. By the end you’ll have a working ntopng deployment, flow export from your edge, dashboards that answer real questions, alerting that doesn’t fire on noise, and a mental model for scaling it. If you’re still running show interface in a loop as your bandwidth strategy, this is for you.

  • A Linux host (Debian 12, Ubuntu 22.04/24.04, or RHEL 9) with at least 4 vCPU, 8 GB RAM, and 100 GB SSD for a small-to-mid deployment.
  • Docker and Docker Compose v2 (for the recommended install path).
  • A router or switch capable of NetFlow v9, IPFIX, or sFlow export.
  • Root or sudo on the collector host, and the ability to open UDP 2055 (NetFlow/IPFIX) and 6343 (sFlow).
  • Basic familiarity with Redis and a time-series database — you’ll be running both.

The tooling landscape for traffic analysis is crowded and mostly mediocre. nfdump/nfsen is fast and scriptable but has a UI from 2008 and no real-time view worth using. Elastic with Packetbeat is fine until you discover the ingest cost of doing it at line rate. PRTG and SolarWinds NTA are commercial products priced for enterprises that mostly want a checkbox. Wireshark is a protocol analyzer, not a monitoring platform — using it for capacity analysis is like using a microscope to survey a highway.

ntopng wins because it does real-time flow analysis with a usable UI, ships with Lua scripting for custom logic, has built-in alerting, and deploys in under ten minutes. It loses because long-term retention is genuinely weak — the built-in timeseries is fine for days, not months. Treat it as the analysis layer and push history somewhere else.

ToolReal-timeRetentionAlertingCostDeploy effort
ntopngExcellentWeak (days)Built-in + LuaFree / paid tiersLow
nfdump/nfsenGoodGood (disk-bound)MinimalFreeMedium
Elastic + PacketbeatGoodExcellentExcellentHigh (ingest)High
PRTGFairGoodGoodPer-sensorLow
SolarWinds NTAGoodGoodGoodExpensiveMedium
WiresharkN/AN/ANoneFreeN/A

The architecture you actually want: your edge router exports flows to nProbe (or softflowd/goflow2 if you’re cheap), nProbe forwards to ntopng over ZMQ, and ntopng dumps timeseries to ClickHouse or InfluxDB for history. ntopng answers “what’s happening now”; the database answers “what happened last Tuesday.”

Here’s a minimal Docker Compose stack that gets you ntopng, Redis, and ClickHouse in one shot:

services:
redis:
image: redis:7-alpine
command: ["redis-server", "--maxmemory", "512mb", "--maxmemory-policy", "allkeys-lru"]
restart: unless-stopped
volumes:
- redis-data:/data
clickhouse:
image: clickhouse/clickhouse-server:24.3
restart: unless-stopped
ulimits:
nofile:
soft: 262144
hard: 262144
volumes:
- clickhouse-data:/var/lib/clickhouse
ntopng:
image: ntop/ntopng:stable
restart: unless-stopped
network_mode: host
depends_on: [redis, clickhouse]
volumes:
- ntopng-data:/var/lib/ntopng
command:
- "--community"
- "-i"
- "eth0"
- "--redis"
- "127.0.0.1"
- "--data-dir"
- "/var/lib/ntopng"
- "--http-port"
- "3000"
- "--disable-login"
- "0"
volumes:
redis-data:
clickhouse-data:
ntopng-data:

The network_mode: host is non-negotiable if you want ntopng to see real interface traffic — bridged networking hides packets behind NAT and you’ll spend an afternoon wondering why your counters are zero. On a multi-tenant host, that’s a real security consideration: ntopng sees everything on the host, so don’t co-locate it with untrusted workloads.

Containerized is the right default. You get a reproducible version, no distro package roulette, and upgrades are a docker compose pull. Set your admin credentials via environment variables rather than the web UI’s first-run wizard, because the wizard has a habit of being skipped on automated deploys.

Terminal window
# Lock down the web UI with nftables before you even start the container
sudo nft add table inet filter
sudo nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
sudo nft add rule inet filter input iif lo accept
sudo nft add rule inet filter input ct state established,related accept
sudo nft add rule inet filter input tcp dport 22 accept
sudo nft add rule inet filter input tcp dport 3000 ip saddr 10.0.0.0/8 accept
sudo nft add rule inet filter input udp dport '{ 2055, 6343 }' accept
sudo nft add rule inet filter input icmp type echo-request accept

That ruleset drops everything by default, allows SSH and the flow-export ports from anywhere, and restricts the ntopng UI to your management subnet. If you’re exposing 3000 to the internet, stop. I’ll say it again later, but you’re one misconfigured reverse proxy away from a ransomware incident.

Bare metal is for high-throughput capture (10 Gbps+), kernel-bypass NICs (DPDK, PF_RING), or environments where containers are banned by policy. The apt repo ritual is the usual GPG key dance:

Terminal window
wget -qO - https://packages.ntop.org/apt-stable/ntop.key | sudo gpg --dearmor -o /etc/apt/keyrings/ntop.gpg
echo "deb [signed-by=/etc/apt/keyrings/ntop.gpg] https://packages.ntop.org/apt-stable/$(lsb_release -cs)/ $(lsb_release -cs)/" | sudo tee /etc/apt/sources.list.d/ntop.list
sudo apt update && sudo apt install -y ntopng nprobe

Then harden the systemd unit. Default units run as root with full filesystem access, which is unnecessary:

/etc/systemd/system/ntopng.service.d/override.conf
[Service]
AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
NoNewPrivileges=yes
ReadWritePaths=/var/lib/ntopng /var/log/ntopng
User=ntopng
Group=ntopng

The AmbientCapabilities line is what lets a non-root user open raw sockets. Without it, you’re back to running as root, and you’ve traded a small privilege for a large one.

The default admin credentials are still admin/admin on a fresh install, and the first-run wizard asks you to change them — but people skip it in automation, or set the same password across a fleet. In 2026 this is still how ntopng instances get popped.

Never expose port 3000 directly. Put it behind a reverse proxy with real authentication (oauth2-proxy, Authelia, or your SSO), TLS, and IP allowlisting. If you can’t do that, use an SSH tunnel and call it a day.

Direct packet capture gives you full visibility at a CPU cost that doesn’t scale past roughly 1 Gbps on commodity hardware. Flow export scales to 100 Gbps with a fraction of the resources, at the cost of payload and per-packet granularity. For finding bandwidth hogs, flow export is almost always the right answer — you don’t need to see what someone downloaded, you need to know who downloaded 400 GB.

nProbe is the paid-but-worth-it collector from the ntop team. It handles NetFlow v5/v9, IPFIX, and sFlow, does ASN and geolocation enrichment, and forwards to ntopng over ZMQ. If you have budget, use it. If you don’t, softflowd on a Linux edge box works fine for smaller deployments.

Terminal window
# nProbe collecting NetFlow/IPFIX on 2055, forwarding to ntopng over ZMQ
nprobe --collector-port 2055 \
--zmq tcp://127.0.0.1:5556 \
-i none \
--dont-nest-flows \
--flow-version 10

The -i none tells nProbe not to capture on a local interface — it’s purely a collector here. --dont-nest-flows disables flow aggregation, which you want for accurate per-flow accounting.

For a Cisco edge exporting NetFlow v9:

Terminal window
ip flow-export version 9
ip flow-export destination 10.10.10.50 2055
ip flow-export source GigabitEthernet0/0/0
ip flow-cache timeout active 1
ip flow-cache timeout inactive 15
interface GigabitEthernet0/0/0
ip flow ingress
ip flow egress

The active 1 timeout means long-lived flows get exported every minute, which is critical — a 6-hour backup job won’t appear in your dashboards until it finishes if you leave the default 30-minute active timeout.

For a Linux edge box without a router that exports flows, softflowd is the pragmatic choice:

Terminal window
softflowd -i eth0 -n 127.0.0.1:2055 -v 9 -t maxlife=60s -d

The “why is my dashboard empty” checklist starts with tcpdump. Yes, actually do this — it takes ten seconds and saves an hour.

Terminal window
sudo tcpdump -i any -nn port 2055 -c 20

If you see packets, flows are arriving. If you don’t, it’s a firewall, a routing problem, or your exporter is misconfigured. Check ntopng’s log next:

Terminal window
sudo tail -f /var/log/ntopng/ntopng.log
ProtocolSamplingOverheadWhat ntopng does with it
NetFlow v5Fixed (1:N)LowBasic flow records, IPv4 only
NetFlow v9Template-basedMediumFull flow records, IPv6, ASN
IPFIXTemplate-basedMediumFull flow records, extensible fields
sFlowPacket samplingLowInterface counters + sampled flows

“Top Talkers” is where you start, not where you finish. A single spike means nothing; a sustained flow means everything. The real-time view is a triage tool — use it to identify the suspect, then dig into the flow detail page to understand the pattern.

The REST API is more useful than the UI for scripting and alerting. Pull active flows and sort by bytes:

Terminal window
curl -s 'http://localhost:3000/lua/rest/v2/get/flow/active.lua?ifid=1' \
| jq -r '.data[] | [.cli_ip, .srv_ip, .proto, .bytes] | @tsv' \
| sort -k4 -nr | head -10

That gives you the top ten active flows by byte count in a format you can pipe into anything. Wire it into a cron job and you have a poor man’s alerting system before you build the real one.

ntopng’s built-in retention is fine for a week, then it’s a liability. Push timeseries to ClickHouse via the timeseries dump, which ntopng supports natively:

# /etc/ntopng/ntopng.conf — timeseries export
--dump-timeseries
--timeseries-dump-format=clickhouse
--timeseries-dump-url=http://127.0.0.1:8123
--timeseries-dump-db=ntopng
--timeseries-dump-table=flows

A minimal ClickHouse schema for flow records:

CREATE TABLE ntopng.flows (
ts DateTime,
ifid UInt32,
cli_ip String,
srv_ip String,
proto UInt8,
bytes UInt64,
packets UInt64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(ts)
ORDER BY (ts, cli_ip, srv_ip)
TTL ts + INTERVAL 90 DAY;

Then “top 20 hosts by egress bytes over 24h” is one query:

SELECT cli_ip, sum(bytes) AS total_bytes
FROM ntopng.flows
WHERE ts >= now() - INTERVAL 24 HOUR
GROUP BY cli_ip
ORDER BY total_bytes DESC
LIMIT 20;

This is where you’ll spend most of your time once you’re past the initial triage. Per-host: protocols, peers, geolocation, historical traffic. The filtering is what makes it useful — exclude your backup servers, your monitoring traffic, and that one dev who discovered BitTorrent in 2026.

Alerting on Bandwidth Hogs Before Users Do

Section titled “Alerting on Bandwidth Hogs Before Users Do”

Out-of-the-box ntopng alerts cover threshold breaches, host behavioral anomalies, and flow-based patterns. They’re a starting point, not a solution. The moment you need “alert me when this specific vendor’s telemetry exceeds 400 Mbps on Tuesdays,” you’re writing Lua.

-- /usr/share/ntopng/scripts/lua/modules/alert_defs/vendor_telemetry.lua
local alerts = require "alerts"
local threshold_bytes = 400 * 1000 * 1000 / 8 -- 400 Mbps in bytes/sec
function check_vendor_telemetry(host)
if host:getIP() == "203.0.113.42" and host:getBytesSent() > threshold_bytes then
alerts.warning("Vendor telemetry burst: " .. host:getIP() ..
" at " .. math.floor(host:getBytesSent() * 8 / 1e6) .. " Mbps")
end
end

The default thresholds are wrong for 90% of environments. Baseline for two weeks before setting a single alert, then set thresholds at 3σ above the mean. If you can’t wait two weeks, start at 2σ and expect to tune.

For Prometheus-based alerting, scrape ntopng’s metrics endpoint and let Alertmanager handle the logic:

# prometheus.yml scrape config
scrape_configs:
- job_name: 'ntopng'
scrape_interval: 30s
static_configs:
- targets: ['ntopng.internal:3000']
# alertmanager rules
groups:
- name: ntopng_bandwidth
rules:
- alert: HostEgressSpike
expr: sum(rate(ntopng_host_bytes_sent[5m])) by (host) > 100e6
for: 10m
labels:
severity: warning
annotations:
summary: "Host {{ $labels.host }} egress > 100 Mbps for 10m"
Alerting approachLatencyFlexibilityOps overhead
ntopng built-inSecondsLowMinimal
ntopng + LuaSecondsHighMedium
Prometheus + Alertmanager30-60sVery highHigh
Syslog + LogstashMinutesMediumHigh

War Story — The Crypto Miner Nobody Noticed

Section titled “War Story — The Crypto Miner Nobody Noticed”

A CI runner in a build cluster started mining Monero. It was subtle: 200 Mbps sustained outbound to a single ASN, capped to look like legitimate traffic. Nobody noticed for three weeks because the WAN link had headroom. What caught it wasn’t a security tool — it was an ntopng host alert on sustained egress to an unfamiliar ASN. The bandwidth anomaly led to the security investigation, not the other way around. Bandwidth analysis is a security control, not just a capacity one. Wire it into your detection strategy.

When ntopng starts dropping packets, you’ll see dashboard latency, growing Redis memory, and dropped counters in the interface view. The knobs that matter: the data directory on fast storage, Redis memory limits, and flow cache size.

# /etc/ntopng/ntopng.conf tuning block
--data-dir=/var/lib/ntopng
--redis=127.0.0.1
--flow-cache-size=131072
--disable-hashing
--max-num-flows=500000
--http-port=3000

Redis needs a memory policy that won’t OOM-kill under load:

Terminal window
redis-cli CONFIG SET maxmemory 2gb
redis-cli CONFIG SET maxmemory-policy allkeys-lru

allkeys-lru means Redis evicts the least-recently-used keys when it hits the limit rather than refusing writes — for ntopng’s cache, that’s the right behavior. Losing a cached flow counter is fine; losing the ability to write new ones is not.

For scaling beyond a single instance, run multiple ntopng collectors (one per flow source) and forward over ZMQ to a central analysis instance. The ZMQ pub/sub model is underrated and rarely used:

Terminal window
# On the collector
nprobe --collector-port 2055 --zmq tcp://*:5556 -i none
# On the analysis instance
ntopng -i zmq://*:5556 --redis 127.0.0.1

Do the storage math before deployment: flows per second × average record size × retention seconds = disk. At 10k fps with 200-byte records over 90 days, that’s roughly 15 TB. If you’re sizing storage after the fact, you’re already wrong.

Exposing port 3000 to the internet. This is negligence, not a misconfiguration. Put it behind oauth2-proxy or don’t expose it. The number of internet-facing ntopng instances with default credentials is a running joke in the security community.

Trusting default alert thresholds. They’re calibrated for a lab. Your production network has different peaks, different flows, different noise. Baseline for two weeks before setting a single threshold.

Using 1:1000 sampling for capacity analysis. On a busy router, that sampling rate will miss your bandwidth hog entirely. Sample at 1:100 or lower for capacity work, 1:1 for security. If your router can’t handle it, buy a bigger router.

Ignoring VLAN granularity. If your switch doesn’t export per-VLAN, you’re guessing. Configure sFlow per-VLAN on Arista or Cisco and you’ll actually know which tenant is the problem.

Forgetting time zones. ntopng stores UTC. Your ops team reads local time. When you correlate an alert with a change ticket, do the math carefully or you’ll chase ghosts.

Q: Why does ntopng show less traffic than my router’s interface counters? Interface counters include all traffic, including flows your exporter doesn’t sample or doesn’t export. If you’re using sFlow at 1:1000, you’re seeing a sample of the total, not the total. Also check that you’re exporting both ingress and egress — many configs default to ingress only.

Q: Can ntopng see encrypted traffic? It sees metadata — source, destination, port, byte counts, TLS SNI in some cases — but not payload. For bandwidth analysis, metadata is all you need. For security analysis, you need flow metadata plus a separate TLS inspection capability.

Q: How much RAM does ntopng need for 1 Gbps? As a rough rule of thumb, 4 GB for 1 Gbps of flow data, 8 GB for 10 Gbps, and scale up from there. Actual usage depends heavily on flow count and retention. Monitor Redis memory and tune maxmemory accordingly.

Q: Does ntopng replace my SIEM? No. ntopng is a network analysis tool, not a log correlation platform. It feeds your SIEM via syslog or webhook, but it doesn’t replace Splunk or Elastic. Use both.

Q: Can I run ntopng without flow export? Yes, using direct packet capture on a mirrored port or a Linux bridge. This works up to roughly 1 Gbps on commodity hardware and gives you full payload visibility. Beyond that, use flow export.

Syslog export to your SIEM is a single config block, and it’s worth doing on day one:

# ntopng.conf — syslog export
--syslog
--syslog-facility=local3
--syslog-priority=info

For auto-ticketing, a webhook to Jira when a host exceeds threshold for more than 15 minutes:

Terminal window
curl -X POST https://yourorg.atlassian.net/rest/api/3/issue \
-u "svc-ntopng@yourorg.com:$JIRA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"project": {"key": "NETOPS"},
"summary": "Bandwidth hog: host exceeded 100 Mbps egress for 15m",
"issuetype": {"name": "Incident"},
"description": "See ntopng host detail for current flow breakdown."
}
}'

For Grafana, scrape ntopng metrics into Prometheus and build three panels: top hosts, top protocols, per-interface throughput. The top-N hosts panel query is a single PromQL expression, and it’s the one you’ll actually look at during an incident.

Configuration management matters once you have more than one instance. An Ansible role with a Jinja2 ntopng.conf template prevents drift and makes upgrades boring — which is the goal. Hand-editing configs across a fleet is how you end up with three different versions of “the same” deployment and no idea which one is correct.

Conclusion — Bandwidth Analysis Is a Discipline, Not a Tool

Section titled “Conclusion — Bandwidth Analysis Is a Discipline, Not a Tool”

ntopng is a scalpel, not a hammer. Use it to answer specific questions — who, what, when, how long — not to stare at pretty graphs and feel productive. Install it cleanly, export flows correctly, tune your alerts against a real baseline, and don’t trust defaults for anything.

The one-paragraph version: your edge router exports flows, nProbe collects them, ntopng analyzes them in real time, ClickHouse stores history, and Prometheus plus Alertmanager handles the paging. That’s the architecture. Everything else is tuning.

Baseline your network for two weeks before you set a single alert. You’ll thank yourself. And yes, you still need to know what “normal” looks like — no tool replaces that.