Introduction

Cable internet subscribers often have no visibility into their modem’s health beyond “the internet works” or “the internet doesn’t work.” But DOCSIS modems expose rich diagnostic data via SNMP — signal-to-noise ratios, power levels, uncorrectable codewords, and channel bonding status. With self-hosted monitoring tools, you can track this data over time, correlate modem health with internet performance, and even generate evidence for ISP support tickets. This guide compares three approaches to self-hosted DOCSIS modem monitoring: Prometheus SNMP Exporter, Telegraf with SNMP input, and custom Python SNMP collectors.

FeaturePrometheus SNMP ExporterTelegraf SNMPCustom Python (pysnmp)
Setup ComplexityMedium (generator required)Low (config file)High (custom code)
VisualizationGrafana dashboardsInfluxDB + GrafanaFlexible (any backend)
AlertingPrometheus AlertManagerKapacitor / GrafanaCustom
SNMP v3 SupportYesYesYes (via pysnmp)
ScalabilityHigh (pull model)Medium (agent-based)Low (single instance)
DOCSIS MIBsManual MIB loadingAuto-discoveryManual parsing
Learning CurveModerateLowHigh

Understanding DOCSIS Modem Metrics

DOCSIS (Data Over Cable Service Interface Specification) modems expose key metrics via SNMP that indicate line quality:

Critical Metrics to Monitor

MetricOIDHealthy RangeWhat It Means
Downstream Power.1.3.6.1.2.1.10.127.1.1.1.1.6-7 to +7 dBmVSignal strength from ISP
Downstream SNR.1.3.6.1.2.1.10.127.1.1.4.1.5>33 dBSignal quality (higher is better)
Upstream Power.1.3.6.1.2.1.10.127.1.2.2.1.335-50 dBmVSignal strength to ISP
Uncorrectable Errors.1.3.6.1.2.1.10.127.1.1.4.1.40 (or near zero)Data corruption events
Corrected Errors.1.3.6.1.2.1.10.127.1.1.4.1.3Low and stableLayer 1 recovery events
Channel Count.1.3.6.1.2.1.10.127.1.1.2.1.116-32+ downstreamBonded channels for speed

Why Monitor Your Modem?

  • Diagnose intermittent issues: Power fluctuations or SNR drops often precede outages
  • Evidence for ISP tickets: Historical graphs of signal degradation are hard for ISPs to dismiss
  • Track upgrades: Confirm when your ISP adds channels or upgrades DOCSIS versions
  • Correlate with speed tests: Link performance drops to specific modem metrics

Approach 1: Prometheus SNMP Exporter

The Prometheus SNMP Exporter is the most scalable option. It uses a generator to create SNMP configurations from MIB files, then exposes metrics in Prometheus format.

Docker Compose Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
version: "3.8"
services:
  snmp-exporter:
    image: prom/snmp-exporter:v0.24.1
    container_name: snmp-exporter
    volumes:
      - ./snmp.yml:/etc/snmp_exporter/snmp.yml:ro
    ports:
      - "9116:9116"
    restart: unless-stopped

  prometheus:
    image: prom/prometheus:v2.52.0
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    restart: unless-stopped

  grafana:
    image: grafana/grafana:11.0.0
    container_name: grafana
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
    ports:
      - "3000:3000"
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

volumes:
  prometheus_data:
  grafana_data:

Generating the SNMP Configuration

1
2
3
4
5
# Download DOCSIS MIBs
wget https://raw.githubusercontent.com/prometheus/snmp_exporter/main/generator/Makefile

# Generate SNMP config for DOCSIS modems
docker run -v $(pwd):/opt prom/snmp-generator generate   --snmp.module=docsis_modem   --snmp.walk=1.3.6.1.2.1.10.127

Prometheus Scrape Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# prometheus.yml
scrape_configs:
  - job_name: "docsis_modem"
    scrape_interval: 60s
    static_configs:
      - targets:
          - "192.168.100.1"  # Cable modem IP
    metrics_path: /snmp
    params:
      module: [docsis_modem]
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: snmp-exporter:9116

Sample Grafana Dashboard Query

1
2
3
4
5
6
7
8
# Downstream power levels (should be -7 to +7 dBmV)
docsIfDownChannelPower{instance="192.168.100.1"}

# Uncorrectable error rate
rate(docsIfDownChannelCorrectedErrors{instance="192.168.100.1"}[5m])

# Signal-to-noise ratio health check
(docsIfSigQSignalNoise{instance="192.168.100.1"} < 33)

Approach 2: Telegraf with SNMP Input

Telegraf offers the simplest setup for beginners. Its SNMP input plugin supports DOCSIS MIBs out of the box with auto-table discovery.

Docker Compose Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
version: "3.8"
services:
  telegraf:
    image: telegraf:1.31
    container_name: telegraf
    volumes:
      - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
    restart: unless-stopped

  influxdb:
    image: influxdb:2.7
    container_name: influxdb
    volumes:
      - influxdb_data:/var/lib/influxdb2
    ports:
      - "8086:8086"
    restart: unless-stopped
    environment:
      - DOCKER_INFLUXDB_INIT_MODE=setup
      - DOCKER_INFLUXDB_INIT_USERNAME=admin
      - DOCKER_INFLUXDB_INIT_PASSWORD=admin123
      - DOCKER_INFLUXDB_INIT_ORG=homelab
      - DOCKER_INFLUXDB_INIT_BUCKET=modem_metrics
      - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-token

  grafana:
    image: grafana/grafana:11.0.0
    container_name: grafana
    volumes:
      - grafana_data:/var/lib/grafana
    ports:
      - "3000:3000"
    restart: unless-stopped

volumes:
  influxdb_data:
  grafana_data:

Telegraf SNMP Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# telegraf.conf
[agent]
  interval = "60s"
  flush_interval = "30s"

# DOCSIS modem SNMP polling
[[inputs.snmp]]
  agents = ["192.168.100.1"]
  version = 2
  community = "public"
  interval = "60s"
  timeout = "10s"
  retries = 3

  # Downstream channels
  [[inputs.snmp.table]]
    oid = "1.3.6.1.2.1.10.127.1.1.1.1"
    name = "docsIfDownChannel"

  [[inputs.snmp.table]]
    oid = "1.3.6.1.2.1.10.127.1.1.4.1"
    name = "docsisDownstreamMetrics"

  # Upstream channels
  [[inputs.snmp.table]]
    oid = "1.3.6.1.2.1.10.127.1.2.2.1"
    name = "docsisUpstreamMetrics"
  
  # Get interface statistics
  [[inputs.snmp.field]]
    oid = "1.3.6.1.2.1.2.2.1.10"
    name = "ifInOctets"
  
  [[inputs.snmp.field]]
    oid = "1.3.6.1.2.1.2.2.1.16"
    name = "ifOutOctets"

# Output to InfluxDB
[[outputs.influxdb_v2]]
  urls = ["http://influxdb:8086"]
  token = "my-super-secret-token"
  organization = "homelab"
  bucket = "modem_metrics"

Approach 3: Custom Python SNMP Collector

For maximum flexibility, a custom Python collector using pysnmp gives you complete control over metric collection and storage.

Python Collector Script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/env python3
"""
DOCSIS modem SNMP collector — polls modem metrics and pushes to
Prometheus Pushgateway or writes to InfluxDB directly.
"""
import time
import json
from pysnmp.hlapi import (
    SnmpEngine, CommunityData, UdpTransportTarget,
    ContextData, ObjectType, ObjectIdentity, nextCmd, getCmd
)

# DOCSIS OIDs to monitor
METRICS = {
    # Downstream: (OID, name, unit, warning_threshold)
    "down_power": ("1.3.6.1.2.1.10.127.1.1.1.1.6", "dBmV", (-7, 7)),
    "down_snr": ("1.3.6.1.2.1.10.127.1.1.4.1.5", "dB", (33, None)),
    "up_power": ("1.3.6.1.2.1.10.127.1.2.2.1.3", "dBmV", (35, 50)),
    "uncorrectable": ("1.3.6.1.2.1.10.127.1.1.4.1.4", "errors", (0, 0)),
    "corrected": ("1.3.6.1.2.1.10.127.1.1.4.1.3", "errors", (None, 1000)),
}

def poll_modem(host="192.168.100.1", community="public"):
    """Walk DOCSIS OIDs and return structured data."""
    results = {}
    
    for metric_name, (oid, unit, thresholds) in METRICS.items():
        values = []
        base_oid = ObjectIdentity(oid)
        
        for (error_indication, error_status, error_index, var_binds) in nextCmd(
            SnmpEngine(),
            CommunityData(community, mpModel=1),
            UdpTransportTarget((host, 161), timeout=2, retries=2),
            ContextData(),
            ObjectType(base_oid),
            lexicographicMode=False,
        ):
            if error_indication:
                print(f"SNMP error: {error_indication}")
                break
            
            for var_bind in var_binds:
                oid_str, val = str(var_bind[0]), var_bind[1]
                values.append({
                    "oid": oid_str,
                    "value": float(val.prettyPrint()),
                    "unit": unit,
                })
        
        results[metric_name] = values
    
    return results

def check_thresholds(results):
    """Check if any metrics are outside healthy ranges."""
    alerts = []
    
    for metric_name, values in results.items():
        _, _, (low, high) = METRICS.get(metric_name, (None, None, (None, None)))
        
        for entry in values:
            val = entry["value"]
            if low is not None and val < low:
                alerts.append(f"LOW {metric_name}: {val} (min: {low})")
            if high is not None and val > high:
                alerts.append(f"HIGH {metric_name}: {val} (max: {high})")
    
    return alerts

if __name__ == "__main__":
    data = poll_modem()
    alerts = check_thresholds(data)
    
    print(json.dumps(data, indent=2))
    
    if alerts:
        print("
⚠️  Alerts:")
        for alert in alerts:
            print(f"  - {alert}")

Docker Compose for Custom Collector

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
version: "3.8"
services:
  modem-collector:
    build: .
    container_name: modem-collector
    volumes:
      - ./metrics:/metrics
    environment:
      - MODEM_IP=192.168.100.1
      - MODEM_COMMUNITY=public
      - POLL_INTERVAL=60
    restart: unless-stopped

  pushgateway:
    image: prom/pushgateway:v1.8.0
    container_name: pushgateway
    ports:
      - "9091:9091"
    restart: unless-stopped

Setting Up Alerting

Regardless of which approach you choose, here are the key alerts to configure:

Critical Alerts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Sample Prometheus alert rules for DOCSIS modem
groups:
  - name: docsis_modem
    rules:
      - alert: DownstreamPowerOutOfRange
        expr: docsIfDownChannelPower < -7 or docsIfDownChannelPower > 7
        for: 10m
        annotations:
          summary: "Downstream power {{ $value }} dBmV is out of range"

      - alert: LowSNR
        expr: docsIfSigQSignalNoise < 33
        for: 5m
        annotations:
          summary: "Downstream SNR {{ $value }} is below healthy threshold"

      - alert: UncorrectableErrorsIncreasing
        expr: rate(docsIfDownChannelUncorrectedErrors[5m]) > 10
        for: 5m
        annotations:
          summary: "Uncorrectable error rate {{ $value }}/s indicates line issues"

      - alert: ChannelLoss
        expr: count(docsIfDownChannelId) < 8
        for: 15m
        annotations:
          summary: "Less than 8 downstream channels bonded — possible modem reset or line issue"

For related reading, see our guides on SNMP collectors with snmp_exporter, snmpcollector, and Telegraf, Prometheus multi-cluster federation monitoring, and self-hosted bandwidth monitoring tools.

FAQ

Q: My modem doesn’t respond to SNMP on 192.168.100.1. What’s wrong?

Many consumer routers block SNMP traffic between LAN and the modem’s management IP. Try connecting a computer directly to the modem (bypassing the router) to confirm SNMP works. Alternatively, some routers allow you to configure a static route to the modem’s IP. Use snmpwalk -v2c -c public 192.168.100.1 to test connectivity.

Q: Do I need DOCSIS MIBs to monitor my modem?

Not strictly. You can poll OIDs directly without MIBs — the numeric OIDs work fine. MIBs only provide human-readable names and type information. For Grafana dashboards, numeric OIDs are sufficient as long as you document what each metric represents.

Q: Can this monitoring detect when my ISP is throttling me?

Indirectly. If you see sudden drops in bonded channels or SNR degradation that correlate with speed test results, you have evidence. However, DOCSIS monitoring shows physical layer metrics, not traffic shaping. Combine modem monitoring with speed test tools like iperf3 or Speedtest CLI for a complete picture.

Q: My modem uses SNMP v3 with authentication. Can these tools handle that?

Yes. Prometheus SNMP Exporter and Telegraf both support SNMP v3 with authentication and encryption. For Prometheus, specify version: 3 in your module config with auth_protocol, priv_protocol, and credentials. For Telegraf, set version = 3 in the [[inputs.snmp]] block with appropriate auth parameters.

Q: How much storage do I need for modem metrics?

A single DOCSIS modem generates about 50-100 metrics per poll at 60-second intervals. This translates to roughly 5-10 MB per month in Prometheus or 15-30 MB in InfluxDB. With a 30-day retention policy, budget about 50-100 MB for a comfortable margin.

Q: Can I monitor multiple modems (e.g., for a small ISP or MDU)?

Absolutely. Each approach scales: Prometheus SNMP Exporter handles thousands of targets natively. Telegraf can run multiple [[inputs.snmp]] blocks for different modems. The Python collector can iterate over a list of IPs. For multi-modem deployments, tag each modem with a location or customer identifier in Grafana.


💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com