If you’re still running SNMP v1 or v2c on your Cisco gear, you’re sending your community strings in cleartext across the wire. SNMP v3 fixes that — authentication, encryption, and per-user access controls. Paired with LibreNMS for discovery and Grafana for dashboards, you get a proper monitoring stack without paying Cisco DNA Center licensing fees.
This guide covers the full path: SNMP v3 engine ID and user config on IOS-XE, trap setup, LibreNMS discovery, and pulling metrics into Grafana via the LibreNMS API. Real commands, real output.
Why SNMP v3 Instead of v2c
SNMP v2c is still everywhere because it’s simple — one community string and you’re polling. The problem is that community string travels in plaintext in UDP packets. Anyone with a span port or a packet capture can read it. On a shared WAN link or an Internet-facing management plane, that’s a credential leak waiting to happen.
SNMP v3 introduces three security models:
- noAuthNoPriv — username only, no real security. Don’t use this in production.
- authNoPriv — authenticates the user (HMAC-MD5 or SHA), but data is unencrypted.
- authPriv — authentication plus AES or DES encryption of the PDU payload. Use this.
For IOS-XE, you’ll want authPriv with SHA-256 and AES-128 at minimum. MD5 is broken and DES is effectively broken — avoid both. SHA-256 support in SNMP requires IOS-XE 16.12 or later; older images only offer SHA-1 (auth sha).
IOS-XE SNMP v3 Configuration
Step 1: Configure the SNMP View
First, define what the monitoring system is allowed to see. An SNMP view controls which OID subtrees are exposed. Creating an explicit view is better practice than relying on the default.
! Expose the full standard MIB-2 tree and Cisco enterprise OIDs
SW1(config)# snmp-server view MONITOR-VIEW 1.3.6.1.2.1 included
SW1(config)# snmp-server view MONITOR-VIEW 1.3.6.1.4.1.9 included
! Verify
SW1# show snmp view
MONITOR-VIEW 1.3.6.1.2.1 - included nonvolatile active
MONITOR-VIEW 1.3.6.1.4.1.9 - included nonvolatile active
If you prefer to expose everything (simpler for lab environments), use iso included instead.
Step 2: Create the SNMP v3 Group
! authPriv group mapped to our view, read-only access
SW1(config)# snmp-server group MONITOR-GROUP v3 priv read MONITOR-VIEW
! Verify
SW1# show snmp group
groupname: MONITOR-GROUP security model:v3 priv
readview : MONITOR-VIEW writeview: <no writeview specified>
notifyview: *tv.aAAAAAAAA.AAAAAgAAAA.gAAAA
row status: active
Step 3: Create the SNMP v3 User
! sha256 = SHA-256, aes 128 = AES-128 — both available on IOS-XE 16.12+
! Replace auth-password and priv-password with strong, unique values
SW1(config)# snmp-server user libreuser MONITOR-GROUP v3 auth sha256 LibreAuthPass123! priv aes 128 LibrePrivPass456!
! IMPORTANT: Users are stored in NVRAM and will NOT appear in running-config
! Verify with:
SW1# show snmp user
User name: libreuser
Engine ID: 80000009030068B6FCBA6140
storage-type: nonvolatile active
Authentication Protocol: SHA256
Privacy Protocol: AES128
Group-name: MONITOR-GROUP
Note the Engine ID — you’ll need this when configuring SNMPv3 informs so the router can authenticate to the remote NMS. Copy it down.
Step 4: Set System Location and Contact
SW1(config)# snmp-server location "Core Switch Room - Rack A3"
SW1(config)# snmp-server contact "noc@example.com"
SW1(config)# snmp-server chassis-id SW1-CORE
Step 5: Configure SNMP Traps
Traps notify your NMS of events rather than waiting for polls. Configure them to send to your LibreNMS server IP (replace 10.10.1.50):
! SNMPv3 inform is more reliable than traps — informs get ACKed by the receiver
SW1(config)# snmp-server engineID remote 10.10.1.50 800000090300C0FFEE000001
! Create a trap user scoped to the remote engine
SW1(config)# snmp-server user trapuser MONITOR-GROUP remote 10.10.1.50 v3 auth sha256 TrapAuthPass789! priv aes 128 TrapPrivPass012!
! Send informs to LibreNMS
SW1(config)# snmp-server host 10.10.1.50 informs version 3 priv trapuser
! Enable common trap categories
SW1(config)# snmp-server enable traps snmp linkdown linkup coldstart warmstart
SW1(config)# snmp-server enable traps config
SW1(config)# snmp-server enable traps envmon
SW1(config)# snmp-server enable traps entity
SW1(config)# snmp-server enable traps bgp
SW1(config)# snmp-server enable traps ospf
! Verify
SW1# show snmp host
Notification host: 10.10.1.50 udp-port: 162 type: inform
user: trapuser security model: v3 priv
traps: snmp bgp ospf config entity envmon
Step 6: ACL to Restrict SNMP Access
Lock SNMP polling to only your monitoring server. This is easy to skip but matters for management plane security (see also Control Plane Policing on IOS-XE if you want a deeper look at protecting the control plane from unauthorized traffic).
SW1(config)# ip access-list standard SNMP-ALLOWED
SW1(config-std-nacl)# permit 10.10.1.50
SW1(config-std-nacl)# deny any log
SW1(config-std-nacl)# exit
! Apply to the SNMP group
SW1(config)# snmp-server group MONITOR-GROUP v3 priv read MONITOR-VIEW access SNMP-ALLOWED
! Quick functional test from the LibreNMS server (Net-SNMP >= 5.8 required for SHA-256):
$ snmpwalk -v3 -l authPriv -u libreuser -a SHA-256 -A LibreAuthPass123! -x AES -X LibrePrivPass456! 10.10.1.1 sysDescr
SNMPv2-MIB::sysDescr.0 = STRING: Cisco IOS Software [Amsterdam], Catalyst L3 Switch Software (CAT9K_IOSXE), Version 17.9.4a
If snmpwalk returns output, the device is ready. If it times out, check the ACL and ensure UDP 161 is open from your monitoring host.
LibreNMS: Adding the Device
LibreNMS is an open-source NMS that auto-discovers Cisco devices, polls SNMP, and handles alerts. Assuming you already have it running (see the Python network automation guide for how it fits into a broader toolset), here’s how to add your newly configured device.
Adding via Web UI
- Navigate to Devices → Add Device
- Enter the device IP or hostname
- Set SNMP Version to
v3 - Set Auth Level to
authPriv - Enter Auth Username:
libreuser - Set Auth Algorithm:
SHA-256 - Enter Auth Password and Priv Password
- Set Priv Algorithm:
AES
Adding via CLI (faster for bulk)
# On the LibreNMS server
cd /opt/librenms
./lnms device:add 10.10.1.1 \
--v3 \
--authlevel authPriv \
--authname libreuser \
--authpass LibreAuthPass123! \
--authalgo SHA-256 \
--cryptopass LibrePrivPass456! \
--cryptoalgo AES
# Expected output:
Adding device 10.10.1.1 ...
Matched sysObjectID 1.3.6.1.4.1.9.1.2424 (Cisco Catalyst 9300 Series)
Device 10.10.1.1 (SW1-CORE) added successfully
Verify Discovery
# Run a manual discovery to validate all modules
./lnms device:discover 10.10.1.1 -vvv
# Force an immediate poll
./lnms device:poll 10.10.1.1
# Check for errors in the discovery log
./lnms device:discover 10.10.1.1 -vvv 2>&1 | grep -i error
After successful discovery, LibreNMS will start polling every 5 minutes by default. You’ll get interface graphs, CPU/memory utilization, environment sensors (temperature, fan speed, PSU status), and BGP/OSPF neighbor state.
Grafana: Building Dashboards from LibreNMS Data
LibreNMS ships with basic graphs, but Grafana gives you significantly more flexibility — custom dashboards, cross-device comparisons, thresholds, and annotations. There are two integration methods: the LibreNMS API datasource plugin, or pulling data directly from the LibreNMS MySQL/MariaDB database.
The API approach is cleaner and doesn’t require direct database access from Grafana.
Install the LibreNMS Datasource Plugin
# On the Grafana server
grafana-cli plugins install larsstegman-librenms-datasource
systemctl restart grafana-server
# Verify plugin loaded
curl -s http://localhost:3000/api/plugins | python3 -c "
import json, sys
plugins = json.load(sys.stdin)
for p in plugins:
if 'librenms' in p.get('id','').lower():
print(p['id'], p.get('info', {}).get('version', ''))
"
larsstegman-librenms-datasource 1.1.0
Generate a LibreNMS API Token
# In LibreNMS web UI: Settings → API → Create API Token
# Or via CLI:
cd /opt/librenms
./lnms user:api-token admin
# Copy the generated token — you'll need it in Grafana
Configure the Datasource in Grafana
- Go to Configuration → Data Sources → Add datasource
- Find LibreNMS in the list
- Set URL to your LibreNMS instance:
https://librenms.example.com - Paste the API token
- Click Save & Test — should return “Data source is working”
Building a Core Interface Dashboard
Here’s a practical panel query to show inbound and outbound traffic on a specific interface:
{
"device": "SW1-CORE",
"port": "GigabitEthernet1/0/1",
"type": "port",
"graph": "port_bits"
}
For a multi-device interface utilization dashboard, use Grafana variables:
- Create a Dashboard Variable: Type = Query, Data Source = LibreNMS, Query =
devices() - Create a second variable: Type = Query, Query =
ports($device) - In your panel query, reference
$deviceand$port— the dropdown filters automatically
Useful Panels to Build
| Panel | Graph Type | Notes |
|---|---|---|
| Interface bit rate (in/out) | Time series | Use port_bits graph type |
| CPU utilization | Gauge + sparkline | device_processor graph |
| BGP session state | Stat panel | Alert on state != Established |
| Interface error rate | Bar chart | port_errors graph, useful for CRC hunting |
| Environment sensors | Gauge | device_sensor for temp/fans/PSU voltage |
Alerting: LibreNMS Alerts to Slack or PagerDuty
LibreNMS has a built-in alerting engine that evaluates rules against collected data. Here are production-ready alert rules you can paste directly into Alerts → Alert Rules:
Interface Down Alert
-- Interface operationally down while admin-enabled (excludes management ports)
macros.device = 1 AND ports.ifAdminStatus = "up" AND ports.ifOperStatus = "down" AND ports.ifDescr != "Mgmt0"
High CPU Alert
-- CPU sustained above 85%
macros.device = 1 AND processors.processor_usage > 85
BGP Peer Down
-- BGP peer not in Established state
macros.device = 1 AND bgpPeers.bgpPeerState != "established"
For alert transport, set up a Webhook transport pointing to your Slack incoming webhook or PagerDuty integration URL under Alerts → Alert Transports. LibreNMS supports templated notification bodies so you can include interface name, current utilization, and last-known good time in the alert message.
Troubleshooting Common Issues
snmpwalk Times Out
! Check if SNMP is receiving packets
SW1# show snmp
Chassis: FDO2350P0TH
Contact: noc@example.com
Location: Core Switch Room - Rack A3
0 SNMP packets input <-- increment this by polling; if it stays 0, check ACL
...
! Verify ACL hit counters
SW1# show ip access-lists SNMP-ALLOWED
Standard IP access list SNMP-ALLOWED
10 permit 10.10.1.50 (0 matches) <-- should increment after each poll attempt
20 deny any log (0 matches)
Wrong Auth Algorithm Error
$ snmpwalk -v3 -l authPriv -u libreuser -a SHA -A LibreAuthPass123! ...
Error in packet: unknownUserName
! -a SHA defaults to SHA-1 in most Net-SNMP builds, but we configured sha256 on the device
! Use the explicit flag:
$ snmpwalk -v3 -l authPriv -u libreuser -a SHA-256 -A LibreAuthPass123! -x AES -X LibrePrivPass456! 10.10.1.1 sysDescr
LibreNMS Shows “SNMP Down” After Discovery
# Enable verbose polling to see what's failing
cd /opt/librenms
./lnms device:poll 10.10.1.1 -vvv 2>&1 | grep -E "SNMP|ERROR|auth"
# SHA-256 requires Net-SNMP >= 5.8 on the LibreNMS host
net-snmp-config --version
# If version < 5.8, fall back to sha (SHA-1) on both the device and LibreNMS
Engine ID Mismatch for Informs
! For informs, the router encrypts using the remote engine's ID
! Fetch LibreNMS's engine ID from snmpd:
snmpget -v3 -l authNoPriv -u libreuser -a SHA-256 -A LibreAuthPass123! \
localhost 1.3.6.1.6.3.10.2.1.1.0
! Use the returned value in:
SW1(config)# snmp-server engineID remote 10.10.1.50 <returned-engine-id>
Scaling to Multiple Devices
For environments with 10+ devices, push the SNMP config via automation rather than logging into each device manually. The patterns from our Python network automation guide with Netmiko, NAPALM, and Nornir apply directly — Netmiko can push the same config block to an entire inventory in minutes.
A quick Netmiko example:
from netmiko import ConnectHandler
SNMP_CMDS = [
"snmp-server view MONITOR-VIEW 1.3.6.1.2.1 included",
"snmp-server view MONITOR-VIEW 1.3.6.1.4.1.9 included",
"snmp-server group MONITOR-GROUP v3 priv read MONITOR-VIEW access SNMP-ALLOWED",
"snmp-server user libreuser MONITOR-GROUP v3 auth sha256 LibreAuthPass123! priv aes 128 LibrePrivPass456!",
"snmp-server enable traps snmp linkdown linkup coldstart",
"snmp-server enable traps config",
"snmp-server host 10.10.1.50 informs version 3 priv trapuser",
]
devices = [
{"device_type": "cisco_ios", "host": "10.10.1.1", "username": "admin", "password": "..."},
{"device_type": "cisco_ios", "host": "10.10.1.2", "username": "admin", "password": "..."},
]
for dev in devices:
with ConnectHandler(**dev) as conn:
conn.send_config_set(SNMP_CMDS)
print(f"{dev['host']}: SNMP v3 configured")
Combined with Cisco EEM scripts, you can trigger LibreNMS API calls directly from the router when specific conditions are met — like auto-removing a device from maintenance mode after an EEM syslog match fires.
Wrapping Up
The full SNMP v3 + LibreNMS + Grafana stack gives you production-grade visibility with no recurring licensing cost. The key takeaways:
- Always use
authPrivwithsha256and AES-128 — anything less is a security gap - SHA-256 in IOS-XE SNMP requires the
auth sha256keyword (notauth sha) and IOS-XE 16.12+ - Lock SNMP polling with an ACL — SNMP credentials exposed to unauthorized hosts are as bad as a default password
- Use informs instead of traps where possible — they provide delivery acknowledgment
- The LibreNMS API datasource in Grafana is cleaner than direct DB access for dashboards
- Automate SNMP config rollout with Netmiko when managing more than a handful of devices
If you're running a mixed environment with Catalyst 9000s alongside older IOS gear, note that older platforms will fall back to SHA-1 when sha256 isn't available. Check the IOS vs IOS-XE vs IOS-XR breakdown if you're unsure which software train you're running and what feature level is supported.