ObservabilityLX · Network ObservabilityNetworkObs
SNMP for Network Devices
What you'll learn
- Describe how snmp_exporter translates SNMP walks into Prometheus metrics using the snmp.yml generator
- Choose SNMPv2c vs SNMPv3 in terms of operational cost and threat model
- Configure an snmp.yml module that polls if_mib for interface traffic and errors
- Diagnose the four boundaries where an SNMP poll can fail: ACL, auth, MIB, version
- Map SNMP interface counters to the same shape as node_network_* for a single dashboard
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
The new switch arrives. It has no agent, no SSH-instrumented metric daemon, and no OpenTelemetry SDK. It has an SNMP MIB and a community string. The lesson that follows is how to bring that switch into the same Prometheus shape as every host on the fleet, without dragging the rest of the platform back into SNMP.
What it is
snmp_exporter is the Prometheus exporter for SNMP. It exposes
a single HTTP endpoint, conventionally /snmp, that accepts a
target, module, and (optionally) auth parameter, performs
one or more SNMP walks and gets against the target, and returns
the result as Prometheus exposition format. Prometheus then
scrapes that endpoint; the act of scraping is the poll.
The exporter translates the device’s SNMP MIB into Prometheus
metrics through a configuration file, conventionally snmp.yml.
The file is generated by generator against a MIB directory.
The generator turns MIB definitions into a walk list and a
metric-mapping section. The exporter uses the walk list to poll
and the metric mapping to label the response.
The canonical alternative is a vendor-specific telemetry agent (gNMI, OpenConfig, streaming telemetry). SNMP is the right approach when the device is older, when the platform does not yet speak gNMI, or when the team is not ready to run a config management pipeline for switch telemetry. SNMPv3 with auth and priv is the right approach when the device supports it.
Why a sysadmin cares
Three production failure classes disappear the day the exporter is in place:
- Interface saturation on a switch. The link to the
database host is at 92 percent. The host metrics say nothing
about it because the saturation is at the switch port, not at
the NIC. The metric that catches this is
ifHCInOctetspolled fromIF-MIB, exposed asif_in_octets. - CRC errors on a long run. A fibre run is degrading. The
switch counts the errors; the host NIC does not see them
because the frames are dropped at the switch port. The
metric that catches this is
ifInErrorsfromIF-MIB. - Routing table instability. A flapping route causes a router to advertise and withdraw prefixes rapidly. The metric that catches this is a counter on routing table size or BGP state transitions.
These metrics already exist on the device. The cost of exposing them is a generated config and a scrape job. The value is that the switch shows up on the same dashboard as the host it connects.
How it works
The exporter exposes /snmp (and a few helpers). Prometheus
sends a request with target, module, and (optionally)
auth query parameters. The exporter looks up the module in
snmp.yml, looks up the auth in auth.yml, opens an SNMP
session, performs the walks and gets, and formats the response.
prometheus.yml
|
v
scrape job: snmp_switch_if
|
| __param_target rewritten from __address__
| __param_module = if_mib
|
v
snmp_exporter :9116/snmp?target=...&module=if_mib
|
| walks: 1.3.6.1.2.1.2.2.1.10 (ifInOctets)
| 1.3.6.1.2.1.2.2.1.16 (ifOutOctets)
| 1.3.6.1.2.1.2.2.1.14 (ifInErrors)
| 1.3.6.1.2.1.2.2.1.20 (ifOutErrors)
| 1.3.6.1.2.1.31.1.1.1.6 (ifHCInOctets, 64-bit)
| 1.3.6.1.2.1.31.1.1.1.10 (ifHCOutOctets, 64-bit)
|
v
if_in_octets{ifIndex="49", ifDescr="GigabitEthernet0/49"} 1.82e+10
if_in_errors{ifIndex="49", ifDescr="GigabitEthernet0/49"} 0
if_out_octets{ifIndex="49", ifDescr="GigabitEthernet0/49"} 9.12e+09
The exporter is stateless between scrapes. There is no caching, no per-target session reuse, and no internal scheduling. Each scrape is a fresh poll. For targets with hundreds of walks, this is the dominant cost.
The if_mib walk
The most polled module is if_mib. It walks the standard
interface table:
ifInOctets,ifOutOctets(32-bit, can wrap)ifHCInOctets,ifHCOutOctets(64-bit, no wrap)ifInErrors,ifOutErrorsifInDiscards,ifOutDiscardsifInUnknownProtosifSpeed,ifHighSpeedifAdminStatus,ifOperStatus
The exporter maps each OID to a Prometheus metric name with labels. The generator constructs the walk list and the metric mapping from the MIB.
How to configure it
Three layers are required: the generated snmp.yml, the auth
file, and the scrape job.
1. The generated snmp.yml
The generator ships as a separate binary or as a make generator target in the snmp_exporter source tree. It reads
MIB definitions and emits snmp.yml:
# Clone the snmp_exporter repository and place vendor MIBs in mibs/.
git clone https://github.com/prometheus/snmp_exporter.git
cd snmp_exporter
cp /var/lib/snmp-mibs/*.mib mibs/
go run ./generator/ --fail-on-parse-errors generate
# Output: snmp.yml
The generated file is hundreds of lines. The relevant section for the interface table:
# snmp.yml (excerpt)
if_mib:
walk:
- 1.3.6.1.2.1.2.2 # IF-MIB::ifTable
- 1.3.6.1.2.1.31.1.1 # IF-MIB::ifXTable (HC counters)
- 1.3.6.1.2.1.2.2.1.1 # ifIndex
- 1.3.6.1.2.1.2.2.1.2 # ifDescr
- 1.3.6.1.2.1.2.2.1.5 # ifSpeed
- 1.3.6.1.2.1.2.2.1.10 # ifInOctets
- 1.3.6.1.2.1.2.2.1.14 # ifInErrors
- 1.3.6.1.2.1.2.2.1.16 # ifOutOctets
- 1.3.6.1.2.1.2.2.1.20 # ifOutErrors
- 1.3.6.1.2.1.31.1.1.1.1 # ifName
- 1.3.6.1.2.1.31.1.1.1.6 # ifHCInOctets
- 1.3.6.1.2.1.31.1.1.1.10 # ifHCOutOctets
- 1.3.6.1.2.1.31.1.1.1.15 # ifHighSpeed
- 1.3.6.1.2.1.31.1.1.1.7 # ifHCInUcastPkts
- 1.3.6.1.2.1.31.1.1.1.11 # ifHCOutUcastPkts
metrics:
- name: if_in_octets
oid: 1.3.6.1.2.1.31.1.1.1.6
type: counter
indexes:
- labelname: ifIndex
type: gauge
lookups:
- labels:
- ifIndex
labelname: ifDescr
oid: 1.3.6.1.2.1.2.2.1.2
type: DisplayString
- labels:
- ifIndex
labelname: ifName
oid: 1.3.6.1.2.1.31.1.1.1.1
type: DisplayString
- name: if_in_errors
oid: 1.3.6.1.2.1.2.2.1.14
type: counter
indexes:
- labelname: ifIndex
type: gauge
The walk list is a list of OIDs. The generator walks the
subtree under each OID and emits one series per leaf. The
ifHCInOctets OID is preferred over ifInOctets because the
HC variant is 64-bit and does not wrap at 100 Mbps.
2. The auth file
auth.yml holds credentials. v2c uses community strings; v3
uses a username, auth protocol, auth password, priv protocol,
and priv password.
# /etc/snmp/auth.yml (v2c example)
snmp_switch_prod:
community: public
version: 2
auth_protocol: ''
priv_protocol: ''
# /etc/snmp/auth.yml (v3 example)
snmp_router_prod:
community: ''
version: 3
security_level: authPriv
username: prometheus
auth_protocol: SHA
auth_password: file:/etc/snmp/auth_password
priv_protocol: AES
priv_password: file:/etc/snmp/priv_password
context_name: ''
The file: prefix tells the exporter to read the password
from a file rather than the YAML. This is the standard form
for production: the YAML is committed, the passwords are not.
3. The scrape job
# /etc/prometheus/prometheus.yml
scrape_configs:
- job_name: snmp_switch_if
metrics_path: /snmp
params:
module: [if_mib]
auth: [snmp_switch_prod]
scrape_interval: 60s
scrape_timeout: 30s
static_configs:
- targets:
- 10.20.30.1
- 10.20.30.2
labels:
device_vendor: cisco
role: aggregation
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__address__]
target_label: instance
- target_label: __tmp_community
replacement: snmp_switch_prod
- source_labels: [__tmp_community]
target_label: auth
The auth parameter selects the named credential from
auth.yml. The __param_target relabel rule rewrites the
target into the exporter’s query parameter.
How to validate it
Three layers must be confirmed: the exporter is up, the walk returned the expected metrics, and Prometheus is receiving the series.
# 1. The exporter is up.
curl -sf http://localhost:9116/metrics | grep -E '^snmp_'
# snmp_exporter_build_info{version="0.26.0"} 1
# snmp_exporter_scrape_walk_duration_seconds{module="if_mib"} 0.214
# 2. The probe works manually.
curl -sf "http://localhost:9116/snmp?target=10.20.30.1&module=if_mib&auth=snmp_switch_prod" \
| grep -E '^if_in_octets'
# if_in_octets{ifIndex="49",ifDescr="GigabitEthernet0/49",instance="10.20.30.1"} 1.82e+10
# 3. Prometheus is receiving the series.
up{job="snmp_switch_if"}
# {instance="10.20.30.1", job="snmp_switch_if"} 1
ifHCInOctets{job="snmp_switch_if"}
# {instance="10.20.30.1", ifIndex="49"} 1.82e+10
# 4. Walk from the command line for cross-check.
snmpwalk -v2c -c public 10.20.30.1 1.3.6.1.2.1.31.1.1.1.6 \
| head -5
# IF-MIB::ifHCInOctets.49 = Counter64: 18234567890
If snmpwalk returns values that the exporter does not, the
auth file is wrong or the walk is filtered by an ACL on the
device. If the exporter returns values but Prometheus is not
receiving them, the scrape job is missing the relabel rules.
How it can fail
Six failure modes appear regularly. Each one is recognisable in the data.
-
ACL on the device. The exporter cannot reach the SNMP port on the device. The walk returns nothing; the scrape returns
up=0for the target. Symptom: every target on a particular subnet isup=0; targets outside the subnet work. -
Wrong auth. The community string is wrong, the v3 password is wrong, or the auth protocol does not match what the device expects. Symptom:
snmp_exporter_scrape_errorsrises for the affected targets; the response body is empty. -
MIB not loaded on the device. The walk references an OID the device does not implement. The exporter returns a partial result; the missing OIDs are simply absent. Symptom: some metrics present, others absent, no error in the exporter logs.
-
SNMP version mismatch. The exporter is configured for v3 but the device only accepts v2c, or vice versa. Symptom:
snmp_exporter_scrape_errorsrises with a “no such name” or “unknown user name” error in the exporter log. -
32-bit counter wrap on a fast interface. The walk references
ifInOctetsinstead ofifHCInOctets. The 32-bit counter wraps every 4 GB at gigabit speeds. Symptom: the rate shows a periodic negative spike; the dashboard plots a sawtooth. -
Walk timeout. The walk includes too many OIDs for the scrape interval to fit. Symptom:
scrape_timeoutfires;up{job="snmp_*"}=0for the affected targets; the exporter log shows “context deadline exceeded”.
How to troubleshoot it
Order matters. Start at the boundary where evidence is most concrete.
- Is the exporter alive?
curl http://exporter:9116/-/readyandcurl http://exporter:9116/metricsfirst. A missingsnmp_exporter_build_infoseries means the exporter is not running. Look at systemd or container logs. - Does the device respond to SNMP at all?
snmpwalk -v2c -c COMMUNITY TARGET 1.3.6.1.2.1.1.1.0(sysDescr). If the device does not respond, the problem is at the network or ACL boundary. - Does the exporter’s auth match? Test with the same
authparameter the scrape job sends:curl "http://exporter:9116/snmp?target=X&module=Y&auth=Z". A 200 with empty body means the walk returned no data; a non-200 means auth or transport failed. - Does the walk list match the device’s MIB? Cross-check
the exporter’s response against
snmpwalkfor the same OID. A mismatch means the generatedsnmp.ymlis out of date relative to the device firmware. - Check the scrape_timeout. If the walk completes in 8 seconds but the scrape timeout is 5 seconds, Prometheus times out. Increase the timeout or split the walk into smaller modules.
- Look at the per-target error counter.
snmp_exporter_scrape_errorsis the headline metric for authentication and walk failures.
Security implications
SNMPv2c traverses the network in plaintext. A community string on a switch in a co-located facility is observable by any device on the same VLAN. Treat every v2c community as a public credential.
SNMPv3 with authPriv is the right answer for production. The
credentials are hashed and encrypted on the wire. The cost is a
configuration change on the device; the benefit is that a
packet capture no longer reveals credentials.
The auth.yml file holds plaintext passwords. The exporter
must run with a restrictive filesystem policy. The file:
prefix decouples the password from the YAML; the password file
must have 0600 permissions and be readable only by the
exporter’s user.
Do not bind the exporter’s HTTP endpoint on the public
interface. The /snmp endpoint is unauthenticated; any caller
can request a walk against any target the exporter can reach.
Performance implications
The dominant cost is the walk itself. A walk over the full
if_mib is a few hundred SNMP GETNEXT requests. A switch with
a thousand ports answers each request in milliseconds; the walk
completes in a few seconds. A slow device or a high-latency
link stretches this to the scrape timeout.
scrape_interval and scrape_timeout must be tuned to the
device. A 60-second scrape interval with a 30-second timeout is
a common starting point. A 10,000-port chassis polled every
30 seconds with 50 OIDs per walk will saturate the device’s
SNMP handler. The right answer is fewer modules per target,
not a shorter scrape interval.
Cardinality scales with ifIndex. A switch with 256 ports
emits 256 series per metric. Twenty metrics in if_mib is
5,120 series per target. A fleet of a hundred switches is half
a million series. Whitelist the ports you alert on, or use a
recording rule to summarise.
Production guidance
- Use SNMPv3 with
authPrivon every production device. The cost is real but the alternative is a plaintext credential on the wire. - Whitelist the SNMP poller’s source address on the device’s ACL. A switch that accepts SNMP from any source is a switch that will be scanned.
- Pin the
ifHCInOctetsandifHCOutOctetsOIDs, not the 32-bit variants. The 32-bit variants wrap on gigabit links in minutes. - Validate the generated
snmp.ymlin CI. The generator can fail on a parse error or a missing MIB; the change is the CI artefact. - Document the auth file convention.
file:prefix,0600permissions, owner is the exporter user. A committed plaintext password is an incident waiting to happen.
Verification
You should now be able to answer:
- Why use
ifHCInOctetsand notifInOctetsfor a gigabit link? - What is the operational difference between SNMPv2c and
SNMPv3 with
authPriv? - How is the
authparameter on the scrape job wired to theauth.yml? - What does
__param_targetdo in the relabel_configs for an snmp_exporter scrape job? - What four boundaries can an SNMP poll failure live at, and which metric reveals each?
Quiz
Knowledge check · 8 questions
Q1. Why prefer ifHCInOctets over ifInOctets on a gigabit link?
Q2. SNMPv2c over a production network is a smell because:
Q3. The snmp_exporter generates snmp.yml automatically from MIB definitions.
Q4. A scrape job sends auth=snmp_router_prod. Where does the exporter resolve the credentials?
Q5. Which OID subtree under IF-MIB holds the per-port 64-bit counters?
Q6. Which of these indicate an SNMP poll failure at the auth or transport boundary? Select all that apply.
Q7. A scrape job runs every 60 seconds with a 5-second scrape_timeout. The walk takes 8 seconds. The likely outcome is:
Q8. The auth.yml file holds SNMPv3 passwords. The production-friendly way to store the password is:
Passing score: 75%. Answers are checked in this browser.