A real-time network monitoring dashboard for Linux, written in Go.
Single-binary deployment with an embedded web UI, optional DNS stats (AdGuard Home, NextDNS, or Pi-hole), WiFi monitoring (UniFi or Omada), GeoIP enrichment, continuous latency monitoring, a macOS menu bar plugin, a Windows system-tray widget, and a GNOME/Linux top-bar indicator.
- Screenshots
- Features
- Quick Start
- Installation
- Configuration
- macOS Menu Bar Plugin
- Windows System Tray Widget
- GNOME/Linux Indicator
- Architecture
- API Endpoints
- External Services Transparency
- Notes
- License
| Traffic (Light) | NAT (Light) | DNS (Light) | WiFi (Light) | Monitor (Light) | Speed Test (Light) | Debug (Light) |
|---|---|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
| Traffic (Dark) | NAT (Dark) | DNS (Dark) | WiFi (Dark) | Monitor (Dark) | Speed Test (Dark) | Debug (Dark) |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
- Live interface stats — queries the kernel via netlink (
RTM_GETLINK) every second; shows RX/TX rates, totals, packets, errors, and drops per interface - Interface grouping — auto-classifies interfaces using netlink
IFLA_INFO_KINDas Physical, VLAN, PPP/WAN, VPN, or Loopback - VPN routing detection — configurable sentinel files to show whether a VPN interface is actively routing traffic
- Real-time line chart — Chart.js with per-interface filtering and 1-hour sliding window
- Per-interface sparklines — mini inline charts on each interface card
- Top talkers by bandwidth — live transfer rates via packet capture with 5-second rate ring for responsive peak detection
- Top talkers by volume — rolling 24-hour totals with 1-minute bucket aggregation
- Host detail modal — click any IP to see per-host stats, 24h traffic volume chart with time labels, and active conntrack flows
- Protocol breakdown — TCP / UDP / ICMP / Other pie chart
- IP version breakdown — IPv4 vs IPv6 traffic split
- GeoIP enrichment — country flags, city names, ASN org names via MaxMind MMDB files (city-level precision with GeoLite2-City)
- Reverse DNS — resolves IPs to hostnames via a shared resolver with TTL-based cache expiry and bounded concurrency
- Traffic world map — live SVG map showing traffic flows with directional RX/TX lines (green for download, orange for upload), city-level coordinates when available, animated flow lines sized by rate, with zoom/pan controls
- Latency monitor — continuous ICMP + HTTPS probes against configurable targets (default: FFMUC anycast, Quad9, Digitalcourage) with rolling sparklines, RTT, jitter, and packet loss; dual-stack IPv4+IPv6; 15-minute history
- AdGuard Home, NextDNS, or Pi-hole integration — total queries, blocked count/percentage, average latency
- Time-series charts — queries and blocked requests over time
- Top clients, domains, and blocked domains — pie charts + ranked detail tables
- Upstream DNS servers — response counts and average latency
- UniFi or Omada controller integration — polls AP and client data from the controller API (first configured wins)
- AP cards — per-AP status, clients, firmware, uptime, IP, MAC, live RX/TX rates
- Clients per AP / per SSID — pie charts and detail tables
- Traffic per AP / per SSID — cumulative bytes + live rates
- Per-client traffic table — hostname, IP, SSID, AP, signal strength (color badges), RX/TX totals, live rates
- Search & sort — filter clients by name/IP/MAC/SSID/AP; sort by traffic, rate, name, or signal
- Conntrack via netlink — uses ti-mo/conntrack to query the kernel's connection tracking table directly via Netlink (no
/proc/net/nf_conntrackneeded) - Connection table overview — total active connections, max table size, usage percentage (color-coded warnings at >50% and >80%)
- IPv4 / IPv6 split — separate counts and sub-tabs for browsing entries by IP version
- Protocol breakdown — TCP / UDP / ICMP / other pie chart
- TCP state distribution — ESTABLISHED, TIME_WAIT, SYN_SENT, CLOSE_WAIT, etc. with color-coded badges
- NAT type detection — classifies each flow as SNAT, DNAT, both, or none by comparing original vs reply tuples
- Per-flow counters — bytes and packets per connection (requires
net.netfilter.nf_conntrack_acct=1) - Top sources & destinations — ranked tables by connection count
- Full entry table — original and reply tuples with translated addresses highlighted, searchable and filterable by NAT type
- macOS menu bar — SwiftBar plugin shows connection count, table usage, IPv4/IPv6 split, and SNAT/DNAT counts
- Server-side speed test — runs download/upload/ping tests from the router against speed.ffmuc.net (OpenSpeedTest)
- Live progress — real-time gauges and progress bar streamed via SSE during the test
- Ping & jitter — measures latency with outlier-resistant median filtering
- Parallel download/upload — 6 concurrent HTTP streams for 10 seconds each for accurate throughput measurement
- Test history — stores the last 50 results in memory with timestamps
- Configurable server — change the target via
SPEEDTEST_SERVERenvironment variable
- Traceroute — native Go ICMP traceroute with configurable probes per hop (default 20), using raw sockets with proper TTL manipulation and ICMP ID matching; shows per-hop IP, reverse DNS hostname (always fresh, bypasses cache), avg/min/max RTT, and packet loss percentage; supports IPv4 and IPv6; streams progress via SSE
- DNS Check — queries a domain (A, AAAA, MX, TXT, NS, CNAME, SOA, PTR) against 14 DNS servers in parallel: System Resolver, FFMUC Anycast01/02 (IPv4+IPv6), Cloudflare (IPv4+IPv6), Google (IPv4+IPv6), Quad9 (IPv4+IPv6), and OpenDNS (IPv4+IPv6); shows comparison matrix, RCode, latency, TTL, DNSSEC AD flag per server; highlights the fastest server and flags records unique to a single server
- Resolver leak check — automatically detects which public IPs your system resolver uses when talking to authoritative servers, via
o-o.myaddr.l.google.comTXT anddnscheck.toolsTXT (including IPv4-only and IPv6-only variants); shows the configured local resolver from/etc/resolv.conf, upstream egress IPs, EDNS Client Subnet info, and resolver org/geo from dnscheck.tools
- Server-Sent Events (SSE) live updates — 1-second refresh with automatic reconnection
- Dark/light/auto theme — saved to localStorage
- Fully embedded UI — all HTML/CSS/JS baked into the binary via
go:embed - macOS menu bar plugin — SwiftBar/xbar script showing live stats
- Windows system tray widget — PowerShell script showing live stats in the notification area- GNOME/Linux indicator -- Python AppIndicator showing live stats in the top bar
- Linux — uses netlink (
RTM_GETLINK,RTM_GETADDR) for interface stats and addresses - nf_conntrack kernel module — for the NAT tab (loaded automatically on most routers)
- Go 1.25+ — to build
# Build
make build
# Download GeoIP databases (optional, free)
make geoip
# Build a stripped binary - smaller binary size (optional).
make build_stripped
# Run (needs root or CAP_NET_RAW + CAP_NET_ADMIN for packet capture and netlink)
sudo ./bandwidth-monitorThen open http://localhost:8080.
Pre-built packages are available from GitHub Releases for:
| Format | Architectures | Platform |
|---|---|---|
.deb |
amd64, arm64 | Debian, Ubuntu, Raspbian |
.rpm |
amd64, arm64 | Fedora, RHEL, AlmaLinux |
.ipk |
x86_64, aarch64, mips_24kc, mipsel_24kc | OpenWrt 23.05 (stable) |
.apk |
x86_64, aarch64 | OpenWrt snapshot (nightly) |
| Nix flake | any | NixOS / Nix on Linux |
APT Repository (recommended):
curl -fsSL https://awlx.github.io/bandwidth-monitor/bandwidth-monitor.gpg.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/bandwidth-monitor.gpg
echo "deb [signed-by=/etc/apt/keyrings/bandwidth-monitor.gpg] https://awlx.github.io/bandwidth-monitor stable main" \
| sudo tee /etc/apt/sources.list.d/bandwidth-monitor.list
sudo apt update
sudo apt install bandwidth-monitorManual install:
sudo dpkg -i bandwidth-monitor_*.deb
sudo vi /etc/bandwidth-monitor/env
sudo systemctl enable --now bandwidth-monitorsudo rpm -i bandwidth-monitor-*.rpm
sudo vi /etc/bandwidth-monitor/env
sudo systemctl enable --now bandwidth-monitoropkg update && opkg install kmod-nf-conntrack-netlink
opkg install /tmp/bandwidth-monitor_*.ipk
vi /etc/bandwidth-monitor/env
/etc/init.d/bandwidth-monitor enable
/etc/init.d/bandwidth-monitor startOptional GeoIP databases:
scp GeoLite2-City.mmdb GeoLite2-ASN.mmdb root@router:/etc/bandwidth-monitor/
/etc/init.d/bandwidth-monitor restartapk update && apk add kmod-nf-conntrack-netlink
apk add --allow-untrusted /tmp/bandwidth-monitor-*.apk
vi /etc/bandwidth-monitor/env
/etc/init.d/bandwidth-monitor enable
/etc/init.d/bandwidth-monitor startThe repo includes a Nix flake with a NixOS module. GeoIP databases are downloaded automatically during the build.
# In your flake.nix inputs:
inputs.bandwidth-monitor.url = "github:awlx/bandwidth-monitor";
# In your NixOS configuration:
{ inputs, ... }: {
imports = [ inputs.bandwidth-monitor.nixosModules.default ];
services.bandwidth-monitor = {
enable = true;
listenAddress = ":8080";
settings = {
ADGUARD_URL = "http://adguard.local";
ADGUARD_USER = "admin";
ADGUARD_PASS = "secret";
};
# Or use an environment file:
# environmentFile = "/etc/bandwidth-monitor/env";
# Use services.geoipupdate for fresh databases instead of bundled ones:
# geoipDir = "/var/lib/GeoIP";
};
}To update the bundled GeoIP databases: nix flake update
Or run directly without installing:
nix run github:awlx/bandwidth-monitor# Build, download GeoIP DBs, install to /opt/bandwidth-monitor,
# set up systemd service, and start
make installThis will:
- Build the binary
- Download GeoIP databases if not present
- Copy everything to
/opt/bandwidth-monitor/ - Create
.envfromenv.example(if it doesn't exist) - Install and enable the systemd service
# Check status
systemctl status bandwidth-monitor
# View logs
journalctl -u bandwidth-monitor -f
# Uninstall everything
make uninstallgo build -o bandwidth-monitor .
sudo mkdir -p /opt/bandwidth-monitor
sudo cp bandwidth-monitor /opt/bandwidth-monitor/
sudo cp env.example /opt/bandwidth-monitor/.env
sudo chmod 0600 /opt/bandwidth-monitor/.env
# Edit .env with your settings
sudo cp bandwidth-monitor.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now bandwidth-monitorThe included bandwidth-monitor.service runs the binary with:
CAP_NET_RAWandCAP_NET_ADMINfor packet capture and netlink access (no full root needed)ProtectSystem=strict,ProtectHome=yes,PrivateTmp=yeshardening- Environment loaded from
/opt/bandwidth-monitor/.env
All configuration is via environment variables. Copy the example file and edit:
cp env.example /opt/bandwidth-monitor/.env
chmod 0600 /opt/bandwidth-monitor/.env| Variable | Default | Description |
|---|---|---|
LISTEN |
:8080 |
HTTP listen address (e.g. 198.51.100.1:8080) |
PROMISCUOUS |
true |
Enable promiscuous mode for packet capture (true/false) |
INTERFACES |
(all) | Comma-separated list of interfaces to monitor and display (e.g. eth0,ppp0,wg0). Controls both the web UI and packet capture. If not set, all interfaces are used. |
GEO_CITY |
GeoLite2-City.mmdb |
Path to GeoLite2 City MMDB (includes country, city, coordinates for map). ~57 MB. For devices with limited flash (e.g. OpenWrt routers), use GeoLite2-Country.mmdb (~6 MB) instead — set GEO_CITY=GeoLite2-Country.mmdb. Country data still works, just without city-level map precision. |
GEO_ASN |
GeoLite2-ASN.mmdb |
Path to GeoLite2 ASN MMDB (~11 MB) |
| Variable | Default | Description |
|---|---|---|
ADGUARD_URL |
(disabled) | AdGuard Home base URL (e.g. http://adguard.example.net) |
ADGUARD_USER |
AdGuard Home username | |
ADGUARD_PASS |
AdGuard Home password | |
NEXTDNS_PROFILE |
(disabled) | NextDNS profile ID (e.g. abc123) |
NEXTDNS_API_KEY |
NextDNS API key (from my.nextdns.io/account) | |
PIHOLE_URL |
(disabled) | Pi-hole base URL (e.g. http://pi.hole) |
PIHOLE_PASSWORD |
Pi-hole password or app password |
| Variable | Default | Description |
|---|---|---|
UNIFI_URL |
(disabled) | UniFi controller URL (e.g. https://unifi.example.net:8443) |
UNIFI_USER |
UniFi controller username | |
UNIFI_PASS |
UniFi controller password | |
UNIFI_SITE |
default |
UniFi site name |
The UniFi integration auto-detects both legacy controllers (port 8443) and UniFi OS devices (UDM/UDR/CloudKey Gen2+, port 443).
| Variable | Default | Description |
|---|---|---|
OMADA_URL |
(disabled) | TP-Link Omada controller URL (e.g. https://omada.example.net) |
OMADA_USER |
Omada controller username | |
OMADA_PASS |
Omada controller password | |
OMADA_SITE |
Default |
Omada site name |
| Variable | Default | Description |
|---|---|---|
LOCAL_NETS |
(auto-detect) | Comma-separated CIDRs for RX/TX direction detection (e.g. 192.0.2.0/24,2001:db8::/48). Auto-discovered from local interfaces if not set. |
SPAN_DEVICE |
(disabled) | SPAN/mirror port interface for direction-aware RX/TX (requires LOCAL_NETS; e.g. eth1) |
VPN_STATUS_FILES |
(none) | Comma-separated iface=path pairs for VPN routing detection (e.g. wg0=/run/wg0-active) |
| Variable | Default | Description |
|---|---|---|
SPEEDTEST_SERVER |
https://speed.ffmuc.net |
Target server URL for the speed test tab |
| Variable | Default | Description |
|---|---|---|
LATENCY_TARGETS |
anycast01.ffmuc.net,anycast02.ffmuc.net,dns.quad9.net,dns3.digitalcourage.de |
Comma-separated hostnames/IPs to probe via ICMP and HTTPS |
- DNS tab — shown when AdGuard Home, NextDNS, or Pi-hole is configured
- WiFi tab — shown when UniFi or Omada is configured
- NAT tab — shown automatically when
nf_conntrackis loaded and the process hasCAP_NET_ADMIN
The NAT tab works out of the box on any Linux system with nf_conntrack loaded — no configuration needed. To enable per-flow byte/packet counters:
# Enable conntrack accounting (required for per-flow bytes/packets)
sysctl -w net.netfilter.nf_conntrack_acct=1
# Make persistent
echo 'net.netfilter.nf_conntrack_acct=1' >> /etc/sysctl.confThe binary needs CAP_NET_ADMIN (or root) for netlink access. The included systemd service already grants this via AmbientCapabilities.
The top-talkers tables show per-host RX (download) and TX (upload) columns. Local network ranges are auto-discovered from interface addresses at startup — no configuration needed in most cases.
For SPAN/mirror port setups or if auto-discovery doesn't cover all your addresses (e.g. dynamic ISP prefixes), set LOCAL_NETS explicitly — similar to iftop's -F/-G flags:
LOCAL_NETS=192.0.2.0/24,2001:db8::/48On a SPAN or mirror port, the kernel reports all mirrored traffic as RX on the interface, making the normal RX/TX split meaningless. Setting SPAN_DEVICE activates a raw-socket overlay that inspects IP headers and classifies direction using LOCAL_NETS:
- src in LOCAL_NETS → remote = upload (TX)
- remote → dst in LOCAL_NETS = download (RX)
- both local = counted as both (intra-LAN)
# In your .env
SPAN_DEVICE=eth1
LOCAL_NETS=192.0.2.0/24,2001:db8::/48All other interfaces keep their normal netlink-based stats, VPN routing detection, interface grouping, etc. Only the SPAN device gets its RX/TX overridden. Requires root or CAP_NET_RAW.
The VPN_STATUS_FILES variable tells bandwidth-monitor which sentinel files to check for active VPN routing. On OpenWrt, a hotplug script (99-vpn-status) is included that automatically creates and removes these sentinel files when WireGuard interfaces go up or down.
The script is installed automatically with the OpenWrt package to /etc/hotplug.d/iface/99-vpn-status. It reads VPN_STATUS_FILES from /etc/bandwidth-monitor/env — the same file the main service uses — so there is nothing extra to configure:
# In /etc/bandwidth-monitor/env
VPN_STATUS_FILES=wg0=/run/wg0-active,wg1=/run/wg1-activeWhen wg0 comes up, the hotplug script writes a timestamp to /run/wg0-active. When it goes down, the file is removed. The dashboard shows a 🔒 icon on interfaces that are actively routing.
A SwiftBar / xbar plugin is included at swiftbar/bandwidth-monitor.5s.sh. It shows live RX/TX rates, DNS stats, and WiFi client counts in the macOS menu bar.
Dependencies: curl, jq (install via brew install jq)
Setup:
- Copy
swiftbar/bandwidth-monitor.5s.shto your SwiftBar plugin directory - Make it executable:
chmod +x bandwidth-monitor.5s.sh - Edit the defaults at the top of the script, or set environment variables
Configuration via environment variables:
| Variable | Default | Description |
|---|---|---|
BW_SERVERS |
http://localhost:8080 |
Comma-separated list of servers to try in order (first reachable wins) |
BW_SERVER |
http://localhost:8080 |
Single server URL (used if BW_SERVERS is not set) |
BW_PORT |
8080 |
Port used when auto-detecting the server from the macOS default gateway |
BW_PREFER_IFACE |
(auto) | Default preferred interface for menu bar title (e.g. ppp0) |
BW_PREFER_IFACE_MAP |
(none) | Per-server interface override: url=iface,url=iface |
BW_SHOW_EXTERNAL_IP |
true |
Show public IPs in menu bar by querying ip.ffmuc.net; set to false to disable |
Multi-server example (edit the defaults in the script):
SERVERS="http://198.51.100.1:8080, http://203.0.113.1:8080"
PREFER_IFACE_MAP="http://198.51.100.1:8080=eth0,http://203.0.113.1:8080=ppp0"The plugin tries each server in order with a 1-second timeout. The preferred interface is resolved per-server from the map. Shows a 🔒 icon when VPN routing is active.
A PowerShell system-tray widget is included at windows/bandwidth-monitor-tray.ps1. It creates a notification area (system tray) icon that polls the bandwidth-monitor API every 5 seconds, showing live RX/TX rates in the tooltip and full details (interfaces, DNS, WiFi, NAT) in the right-click context menu.
Dependencies: PowerShell 5.1+ (built into Windows 10/11), .NET Framework (for System.Windows.Forms)
Setup:
- Double-click
windows/bandwidth-monitor-tray.vbsfor a silent launch (no console window) - Or use
windows/bandwidth-monitor-tray.batto launch with a visible console (useful for debugging) - Or run directly in PowerShell:
powershell -ExecutionPolicy Bypass -File bandwidth-monitor-tray.ps1 - The icon auto-detects the server from the Windows default gateway, or set it explicitly
Configuration via parameters or environment variables:
| Parameter | Env Variable | Default | Description |
|---|---|---|---|
-Server |
BW_SERVER |
(auto-detect from default gateway) | Base URL of the bandwidth-monitor server |
-Port |
BW_PORT |
8080 |
Port used when auto-detecting from the gateway |
-PreferIface |
BW_PREFER_IFACE |
(auto) | Preferred interface name for the tooltip |
-RefreshSeconds |
— | 5 |
Polling interval in seconds |
-ShowExternalIP |
BW_SHOW_EXTERNAL_IP |
true |
Show public IPs by querying anycast-v4.ffmuc.net / anycast-v6.ffmuc.net; set to false to disable |
Example:
.\bandwidth-monitor-tray.ps1 -Server http://198.51.100.1:8080 -PreferIface eth0Features:
- Tooltip shows current download/upload rates for the primary interface (e.g.
eth0: down 12.3 Mb/s / up 4.5 Mb/s) - Live icon renders compact down/up rates with coloured arrows (green ↓, orange ↑) directly on the tray icon
- DPI-aware icon scales with Windows display scaling (125%, 150%, 200%)
- Right-click menu shows all interfaces, external IPs, DNS stats, WiFi clients, NAT table info
- Double-click opens the web dashboard in the default browser
- Shows
[VPN]in the tooltip when VPN routing is active
Auto-start on login:
- Copy the
windowsfolder to your user directory (e.g.%USERPROFILE%\bandwidth-monitor\):Copy-Item -Recurse .\windows "$env:USERPROFILE\bandwidth-monitor"
- Create a startup shortcut (copy-paste this as-is into PowerShell):
$ws = New-Object -ComObject WScript.Shell $sc = $ws.CreateShortcut("$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\bandwidth-monitor-tray.lnk") $sc.TargetPath = "$env:USERPROFILE\bandwidth-monitor\bandwidth-monitor-tray.vbs" $sc.WindowStyle = 7 # minimized $sc.Save()
Or manually: press Win+R, type shell:startup, and copy bandwidth-monitor-tray.bat (or a shortcut to it) into that folder.
Tip: Windows may hide new tray icons in the overflow area. Click the ^ arrow near the clock to find it, then drag the icon onto the taskbar to keep it visible.
A Python AppIndicator is included at gnome/bandwidth-monitor-indicator.py. It shows live RX/TX rates in the GNOME top bar (or any panel supporting AppIndicator) and full details in the dropdown menu.
Works on GNOME (with the AppIndicator extension), KDE Plasma, XFCE, Budgie, Cinnamon, and MATE.
Dependencies:
# Debian/Ubuntu
sudo apt install python3-gi gir1.2-gtk-3.0 gir1.2-ayatanaappindicator3-0.1
# Fedora
sudo dnf install python3-gobject gtk3 libayatana-appindicator-gtk3
# Arch
sudo pacman -S python-gobject gtk3 libayatana-appindicatorOn GNOME Shell 42+, install the AppIndicator and KStatusNotifierItem Support extension.
Setup:
- Run directly:
./gnome/bandwidth-monitor-indicator.py - Or with options:
./gnome/bandwidth-monitor-indicator.py --server http://198.51.100.1:8080 - The indicator auto-detects the server from the Linux default gateway
Configuration via CLI flags or environment variables:
| Flag | Env Variable | Default | Description |
|---|---|---|---|
--server |
BW_SERVER |
(auto-detect from default gateway) | Base URL of the bandwidth-monitor server |
--port |
BW_PORT |
8080 |
Port used when auto-detecting from the gateway |
--prefer-iface |
BW_PREFER_IFACE |
(auto) | Preferred interface name for the panel label |
--refresh |
-- | 5 |
Polling interval in seconds |
--show-external-ip |
BW_SHOW_EXTERNAL_IP |
true |
Show public IPs via anycast-v4.ffmuc.net / anycast-v6.ffmuc.net |
Auto-start on login:
# Copy the indicator to a system-wide location
sudo mkdir -p /usr/local/share/bandwidth-monitor
sudo cp gnome/bandwidth-monitor-indicator.py /usr/local/share/bandwidth-monitor/
sudo chmod +x /usr/local/share/bandwidth-monitor/bandwidth-monitor-indicator.py
# Install the .desktop file for autostart
cp gnome/bandwidth-monitor-indicator.desktop ~/.config/autostart/Or for the current user only, symlink the script and edit the Exec= path in the .desktop file.
Features:
- Panel label shows live compact down/up rates with arrows (e.g.
\u21935M \u219112M) - Dropdown menu shows all interfaces, external IPs, DNS stats, WiFi clients, NAT info
- Click "Open Dashboard" to launch the web UI in the default browser
- Shows
[VPN]in the panel label when VPN routing is active - Uses standard
network-transmit-receiveicon from the system theme
main.go → entry point, env config, wires all components
collector/ → netlink-based interface stats (RTM_GETLINK/RTM_GETADDR), rates, 24h history, VPN routing
conntrack/ → netlink-based conntrack (NAT) table reader via ti-mo/conntrack
talkers/ → AF_PACKET raw-socket capture, per-IP tracking, 1-min bucket aggregation + 5s rate ring for responsive peaks
resolver/ → shared reverse-DNS resolver with TTL-based cache and bounded concurrency
latency/ → continuous ICMP + HTTPS latency monitoring with rolling history
speedtest/ → HTTP-based speed test client (download/upload/ping against OpenSpeedTest servers)
debug/ → traceroute (native ICMP), DNS checker (multi-server), resolver leak detection
handler/ → HTTP REST API + SSE streaming handler
dns/ → common DNS provider interface
adguard/ → AdGuard Home API client (stats, top clients/domains)
nextdns/ → NextDNS API client (stats, top clients/domains)
pihole/ → Pi-hole v6 API client (stats, top clients/domains, upstreams)
wifi/ → common WiFi provider interface
unifi/ → UniFi controller API client (APs, SSIDs, clients, live rates)
omada/ → TP-Link Omada controller API client (APs, SSIDs, clients, live rates)
geoip/ → MaxMind MMDB GeoIP lookups (country, city, coordinates, ASN)
static/
index.html → HTML shell with seven tabs (Traffic, NAT, DNS, WiFi, Monitor, Speed Test, Debug)
app.js → all frontend JavaScript (charts, tables, SSE client)
style.css → full stylesheet (dark/light themes)
swiftbar/ → macOS menu bar plugin
windows/ → Windows system tray widget
gnome/ → GNOME/Linux top-bar indicator
packaging/
openwrt-Makefile → OpenWrt package definition
openwrt-files/
bandwidth-monitor.init → procd init script for OpenWrt
99-vpn-status → OpenWrt hotplug script for VPN sentinel files
postinstall.sh → deb/rpm post-install script
preremove.sh → deb/rpm pre-remove script
nfpm.yaml → deb/rpm packaging config (nfpm)
.github/workflows/ → CI: builds deb, rpm, ipk, apk on push & tag
env.example → example environment configuration
bandwidth-monitor.service → systemd unit file
flake.nix → Nix flake with package + NixOS module
Makefile → build, install, GeoIP download targets
| Endpoint | Method | Description |
|---|---|---|
/api/interfaces |
GET | Current stats for all interfaces |
/api/interfaces/history |
GET | 24h time-series per interface |
/api/talkers/bandwidth |
GET | Top 10 by current bandwidth |
/api/talkers/volume |
GET | Top 10 by 24h volume |
/api/dns |
GET | DNS summary (AdGuard Home, NextDNS, or Pi-hole) |
/api/wifi |
GET | WiFi summary (UniFi or Omada) |
/api/latency |
GET | Latency monitoring status (ICMP + HTTPS probes) |
/api/conntrack |
GET | NAT / conntrack summary (connections, states, NAT types, entries) |
/api/speedtest/run |
POST | Start a speed test; streams progress as SSE (Server-Sent Events) |
/api/speedtest/results |
GET | Speed test history (last 50 results) and running status |
/api/debug/traceroute |
POST | ICMP traceroute with SSE progress; params: target, count (probes/hop), maxttl |
/api/debug/dns |
GET | DNS check against 14 servers + resolver leak test; params: domain, type |
/api/summary |
GET | Compact summary for menu bar clients |
/api/events |
GET | SSE stream — pushes all data every second (Server-Sent Events) |
Every hardcoded external service that bandwidth-monitor or its components contact:
| Service | URL / IP | Component | Trigger | Data sent | Data received |
|---|---|---|---|---|---|
| FFMUC Speed Test | speed.ffmuc.net |
Speed Test tab | User clicks "Start Test" | HTTP GET /downloading, POST /upload (random payload) |
Download payload, upload ack |
| FFMUC IP Check | ip.ffmuc.net |
SwiftBar plugin | Every ~5 min (cached), on by default (BW_SHOW_EXTERNAL_IP=false to disable) |
HTTPS GET (IPv4 + IPv6) | Router's public IPv4 and IPv6 address |
| FFMUC IP Check | anycast-v4.ffmuc.net, anycast-v6.ffmuc.net |
Windows tray widget | Every ~5 min (cached), on by default (BW_SHOW_EXTERNAL_IP=false to disable) |
HTTPS GET (one per address family) | Router's public IPv4 and IPv6 address |
| FFMUC IP Check | anycast-v4.ffmuc.net, anycast-v6.ffmuc.net |
GNOME indicator | Every ~5 min (cached), on by default (--show-external-ip false to disable) |
HTTPS GET (one per address family) | Router's public IPv4 and IPv6 address |
| FFMUC Anycast01 | 5.1.66.255, 2001:678:e68:f000:: |
DNS Check | User clicks "Query" | DNS query for user-entered domain | DNS records |
| FFMUC Anycast02 | 185.150.99.255, 2001:678:ed0:f000:: |
DNS Check | User clicks "Query" | DNS query for user-entered domain | DNS records |
| Cloudflare DNS | 1.1.1.1, 2606:4700:4700::1111 |
DNS Check | User clicks "Query" | DNS query for user-entered domain | DNS records |
| Google DNS | 8.8.8.8, 2001:4860:4860::8888 |
DNS Check | User clicks "Query" | DNS query for user-entered domain | DNS records |
| Quad9 DNS | 9.9.9.9, 2620:fe::fe |
DNS Check | User clicks "Query" | DNS query for user-entered domain | DNS records |
| OpenDNS | 208.67.222.222, 2620:119:35::35 |
DNS Check | User clicks "Query" | DNS query for user-entered domain | DNS records |
| Google Authoritative | o-o.myaddr.l.google.com |
Resolver leak check | Piggybacks on DNS Check | TXT query via system resolver | Resolver's public IP, ECS info |
| dnscheck.tools | test.dnscheck.tools, test-ipv4.*, test-ipv6.* |
Resolver leak check | Piggybacks on DNS Check | TXT query via system resolver | Resolver IP, org, geo, protocol |
| FFMUC Anycast01 | anycast01.ffmuc.net |
Latency monitor | Every 2s on startup (on by default, configurable via LATENCY_TARGETS) |
ICMP echo + HTTPS GET | RTT measurement |
| FFMUC Anycast02 | anycast02.ffmuc.net |
Latency monitor | Every 2s on startup (on by default, configurable via LATENCY_TARGETS) |
ICMP echo + HTTPS GET | RTT measurement |
| Quad9 DNS | dns.quad9.net |
Latency monitor | Every 2s on startup (on by default, configurable via LATENCY_TARGETS) |
ICMP echo + HTTPS GET | RTT measurement |
| Digitalcourage DNS | dns3.digitalcourage.de |
Latency monitor | Every 2s on startup (on by default, configurable via LATENCY_TARGETS) |
ICMP echo + HTTPS GET | RTT measurement |
All JavaScript libraries (Chart.js, Luxon) and fonts (Inter, JetBrains Mono) are bundled in the binary — no CDN requests are made at runtime. The world map boundary data (Natural Earth 110m, public domain) is also bundled. See THIRD_PARTY_LICENSES.md for their licenses.
User-configured services (AdGuard Home, NextDNS, Pi-hole, UniFi Controller, Omada Controller) are not listed here — they are optional and only contacted when explicitly configured via environment variables.
FFMUC services (speed.ffmuc.net, ip.ffmuc.net, Anycast DNS) are operated by Freie Netze München e.V. — see their privacy policy.
No telemetry, no analytics, no crash reporting, no update checks. The binary phones home to nothing.
| Feature | Capability required |
|---|---|
| Interface stats, NAT tab | CAP_NET_ADMIN (or root) |
| Top talkers, SPAN mode | CAP_NET_RAW (or root) |
| Traceroute, Latency monitor (ICMP) | CAP_NET_RAW |
| DNS check, resolver leak test | No special permissions |
If running without root, grant both CAP_NET_RAW and CAP_NET_ADMIN for full functionality.
- GeoIP — without MMDB files, country/ASN columns are simply hidden. Download the free GeoLite2 databases from MaxMind (requires free account) or run
make geoip - DNS and WiFi tabs — only appear when their respective integrations are configured
- Speed test — runs from the router, not the client — useful for testing WAN throughput independent of local WiFi
- NAT per-flow counters — require
nf_conntrack_acct=1(see Conntrack Configuration) - All assets are embedded in the binary — single-file deployment, no runtime dependencies
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
You are free to use, modify, and distribute this software under the terms of the AGPL-3.0. If you modify the program and make it available over a network, you must release your modifications under the same license.
Bundled third-party libraries (Chart.js, Luxon, Inter, JetBrains Mono) are distributed under their respective permissive licenses — see THIRD_PARTY_LICENSES.md for details.













