Per-Device Bandwidth Monitoring With a Managed Switch
The switch already counts every byte on every port — you just have to ask it

Contents
There is a particular flavour of homelab frustration where the whole connection feels like treacle and you have no idea which machine is to blame. The router’s traffic graph shows a fat spike — 300 Mbit sustained for twenty minutes — and that is the sum total of what it will tell you. Something is chewing the uplink. Which something? The router hasn’t the faintest, because from its point of view the entire LAN is a single blob of NAT’d traffic arriving on one interface.
The device that does know is the switch. Every managed switch keeps a running byte counter on every physical port, updated in hardware, and it will hand those counters over the moment you learn to ask. That’s the whole trick to per-device bandwidth monitoring at home: one device sits on one port, the port counts its bytes, and you read the counters on a schedule. No agent on the endpoint, no packet capture, no deep-packet-inspection appliance. Ground truth at the wire.
Why the switch is the right place to measure
Measurement location matters more than measurement tooling. You can install a bandwidth agent on every machine, but the moment a device can’t run an agent — a TV, a games console, a hypervisor’s black-box guest, a smart plug — your picture goes blind exactly where you most want to see. Measuring at the switch sidesteps all of that. If a thing has an Ethernet cable, it has a port, and the port has counters.
The counters themselves are gloriously dumb, which is their strength. A switch increments ifHCInOctets and ifHCOutOctets for each port as frames cross the silicon. It doesn’t care what protocol you’re running, whether the traffic is encrypted, or whether the endpoint would rather you didn’t know. This makes it the honest broker in your network. When your DoH resolver hides DNS content from the sinkhole, the switch still sees the bytes leaving the port — it just can’t read them, and for bandwidth accounting reading them is beside the point.
The catch, and it’s worth stating up front, is that this gives you volume per port while telling you nothing about destination per flow. The switch will tell you that the machine on port 7 pulled 40 GB last night. It will not tell you that 39 of those gigabytes came from one Linux ISO mirror. For destination-level accounting you need flow export (sFlow/NetFlow) or a proper firewall doing the counting, and that’s a heavier setup for a different question. For “which box is hammering the line”, port counters are exactly right and almost free.
What you actually need
A managed or smart-managed switch. This is the load-bearing requirement. Unmanaged switches — the fanless £15 boxes — keep no counters you can read and speak no management protocol. You want a switch that supports SNMP, which in practice means anything sold as “smart managed”, “web managed”, or “L2+ managed”. Most of them expose SNMP even if the marketing leads with a web GUI. Prices for an eight- or sixteen-port smart switch have come down far enough that it’s the cheapest observability upgrade in the rack.
The protocol we lean on is SNMP, the Simple Network Management Protocol, and specifically the interface counters in the standard IF-MIB. Every compliant switch exposes them. The important OIDs:
| OID name | What it counts | Notes |
|---|---|---|
ifDescr | Human name of the port | e.g. GigabitEthernet0/7 |
ifHCInOctets | Bytes received on the port | 64-bit — use this one |
ifHCOutOctets | Bytes transmitted | 64-bit |
ifInOctets | Bytes received | 32-bit — wraps, avoid at gigabit |
ifSpeed | Nominal port speed | for utilisation % |
ifOperStatus | up / down | to ignore dark ports |
The HC in ifHCInOctets stands for High Capacity, meaning a 64-bit counter, and it is the single most important detail in this entire article. I’ll come back to why in the troubleshooting section, because getting it wrong produces graphs that look plausible and are quietly lying.
Turn on SNMP without opening a hole
SNMP has three versions in the wild. v1 is museum-grade and you should ignore it. v2c is what most homelabs run: simple, widely supported, and authenticated by a plaintext “community string” that functions as a shared password. v3 adds real authentication and encryption and is what you want if the management traffic crosses anything untrusted.
For a read-only poll of interface counters on a management network you control, v2c is a defensible choice, provided you treat the community string as a credential and don’t leave it as public. On the switch, under its SNMP settings, you’d configure something like a read-only community and lock access to your monitoring host. Genericising the specifics:
| |
A few habits that keep this safe. Make the community string read-only (ro) — there is never a reason for your bandwidth poller to have write access. Bind SNMP to your management VLAN so it never touches untrusted segments; an unauthenticated UDP service should not be reachable from your IoT gear or your guest network. And restrict it by source IP to the one host doing the polling. UDP/161 exposed to a hostile segment with a guessable community is a genuine information leak — it hands over your entire network topology to anyone who asks.
Prove it works before building anything on top. From your monitoring host:
| |
Two clean numbers back means the switch is answering and you know the ifIndex (here, 7) for the port you care about. Everything downstream is just reading those numbers on a timer and subtracting.
From counters to graphs
The counters are monotonic totals — bytes since the switch last booted. To turn a total into a rate you sample twice and divide the difference by the time between samples. Poll ifHCInOctets.7 at 184,203,847,721, wait 30 seconds, poll again at 184,209,847,721, and you moved 6,000,000 bytes in 30 seconds, or 200 KB/s, or 1.6 Mbit/s. You do not want to compute this by hand forever, so the job goes to a collector.
The pairing I keep coming back to is Telegraf → InfluxDB → Grafana. Telegraf’s inputs.snmp plugin polls the switch, InfluxDB stores the time series, and Grafana draws the per-port graphs. The whole thing runs in three small containers. A Telegraf config for exactly this:
| |
Telegraf understands that octet counters are counters, so in Grafana you apply a derivative (or non_negative_derivative) to get bytes-per-second, multiply by 8 for bits, and you have a live per-port throughput chart. Tag each series by ifName and every port becomes its own line. Thirty-second polling is a sensible default — frequent enough to catch a sustained transfer, gentle enough that you’re not hammering a switch CPU that was designed to forward frames rather than answer SNMP all day.
If Telegraf-plus-Influx feels like a lot of moving parts, LibreNMS bundles the SNMP polling, storage, and auto-discovered graphs into one appliance and will draw these port graphs with zero query-writing. It’s heavier to run but hands you the dashboards for free. For the wider “poll all my dumb infrastructure over SNMP” project this is the natural hub, and I go deep on it in SNMP is not dead: monitoring the dumb boxes. If you already run Netdata for per-second host metrics, note that its SNMP collector can pull these same counters, so you may not need a second stack at all.
Mapping ports to devices, and keeping the map honest
A graph labelled GigabitEthernet0/7 is only useful if you remember what lives on port 7. Two ways to keep the mapping honest. The low-tech way: a text file, or the port descriptions on the switch itself — most managed switches let you set an ifAlias per port, so you can label port 7 “NAS” right there and have it show up in the poll via IF-MIB::ifAlias. The self-documenting way: pin devices to ports mentally by pinning their IPs via static DHCP reservations, so the MAC-to-IP map never drifts.
Where it gets slippery is uplinks and trunks. The port that carries traffic to the rest of the network — your uplink to the router, or a link to another switch — will show enormous counters, because it aggregates everyone. That’s correct and expected; just don’t mistake the uplink port for a single greedy device. Likewise, if two switches are linked, a device’s traffic can appear on the inter-switch link as well as on its own access port, so summing every port double-counts. Graph access ports for per-device numbers and treat trunk ports as a separate category.
Troubleshooting
The counters aren’t moving. First check you’re reading the right ifIndex. Switches don’t always number ports the way the front panel does — port 7 on the faceplate might be ifIndex 9 in the MIB. Walk ifDescr and match the human name to the index. Second, confirm the port is actually up (ifOperStatus); a poll against a dark port returns a stubbornly frozen counter.
The graph has periodic downward cliffs to zero, then huge spikes. This is counter wrap, and it is why I laboured the HC point earlier. A 32-bit ifInOctets counter tops out at about 4.29 billion — roughly 4 GB. On a gigabit port running near line rate that’s about 34 seconds before it rolls over to zero. If your poll interval straddles a rollover, the naïve “new minus old” goes negative, and a badly configured collector renders that as either a cliff or an absurd spike. The fix is to poll the 64-bit ifHCInOctets/ifHCOutOctets, which won’t wrap in any human timeframe, and to use non_negative_derivative so a genuine reset (a switch reboot) is discarded rather than graphed as a mountain.
SNMP times out intermittently. Cheap switches have feeble management CPUs and will drop SNMP responses under forwarding load or when polled too aggressively. Back the interval off to 30 or 60 seconds, raise the timeout, and don’t run five collectors against one switch. If it still flakes, walk a smaller subtree — poll only the ports you care about rather than the whole ifTable.
Everything returns “No Such Object” or times out entirely. Community string mismatch, wrong SNMP version, or an ACL blocking your host. Test with snmpget against a single OID from the monitoring host itself to isolate whether it’s a credentials problem or a reachability problem. If v2c refuses to behave and the switch supports it, moving to v3 sometimes clears up a vendor’s flaky v2c implementation, at the cost of a fiddlier config.
Two ports show identical traffic. You’re looking at a link-aggregation group (LAG/LACP) or a mirror port. Aggregated members carry a share of the same logical link; graph the aggregate interface if the switch exposes one, or accept that the members split the load.
Is it worth it?
For anyone running more than a handful of devices, yes, and the reason is diagnostic speed. The next time the line feels slow, you don’t guess — you glance at the per-port graph and the culprit is the one line climbing toward the ceiling. That alone has paid back the modest cost of a smart switch several times over in my rack.
The honest caveat is what it doesn’t do. Port counters measure volume, and that is the whole of what they do: they name the device eating your bandwidth while staying silent about the destination it’s talking to. When you need the latter you’re into sFlow or firewall-level accounting, which is a bigger project. And if every device you own already runs a metrics agent, you may not need switch counters at all. But agents lie by omission — they miss the appliances that can’t run them — and the switch never does. It counts everything with a cable, quietly, in hardware, whether the endpoint cooperates or not. For the price of a managed switch you were probably going to buy anyway, that honesty is the best-value observability you can add to a home network.




