Skip to main content
RunBook Academy

OPNsenseXXXIV · Monitoring and Observability IntegrationPrometheus and time-series collection

Prometheus exporter on OPNsense — getting the firewall into a time-series database

Intermediate⏱ ~14 minprometheuscurlopnsense-exporterconfigctl

What you'll learn

  • Describe the opnsense-exporter plugin and the metrics it exposes
  • Configure the Prometheus scrape job for the OPNsense target
  • Recognise the label cardinality traps that a firewall can produce
  • Distinguish exporter metrics from PF counters from sysctl values

Prerequisites

Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14

Not yet marked complete on this device.

OPNsense’s built-in graphs are useful for one operator at one moment. The moment the operator wants to graph a metric across thirty days, correlate firewall-health with network-health, or alert on a threshold that crosses a boundary — the built-in graphs are not enough. The answer is a time-series database. The most common one in production today is Prometheus, and OPNsense has a plugin that produces metrics in the Prometheus format: os-opnsense-exporter.

This lesson covers what the exporter exposes, how the Prometheus scrape is configured, what the label-cardinality traps are, and how the exporter’s metrics relate to the underlying PF, sysctl, and FreeBSD sources.

What the exporter exposes

The os-opnsense-exporter plugin runs a small HTTP server on a configurable port (default 9100) on the OPNsense firewall. The endpoint is /metrics and the response is a Prometheus-formatted text document. The exporter pulls data from the OPNsense configuration API, from PF counters, from sysctl values, and from vmstat-style system calls, and exposes them as Prometheus metrics.

The metric categories the exporter covers:

  • PF state table — current entries, searches, inserts, removals, memory, limits.
  • PF rule statistics — per-rule evaluations, matches, bytes, packets.
  • Interface counters — bytes in/out, packets in/out, errors, drops per interface.
  • System — CPU utilisation, memory used/free, load averages, uptime.
  • ARP table — entries per interface.
  • DHCP leases — active leases per interface, lease utilisation.
  • VPN — IPsec tunnel state, WireGuard peer state, OpenVPN session count.
  • Unbound — query rate, cache hits/misses, uptime.
  • Suricata — alert count, packet count, dropped count.
  • CARP — state per VHID, demotion.

The exporter is a Python or PHP process that re-queries these sources every scrape. The cost is real — every scrape triggers several system commands and config reads — but at default 15-second intervals on a modern OPNsense box, the overhead is negligible.

Installing and configuring the plugin

The plugin is installed from the OPNsense web UI: System → Firmware → Plugins → os-opnsense-exporter. Once installed, it appears under Services → Prometheus Exporter. The configuration is minimal:

  • Listen address — defaults to 0.0.0.0; in production bind to the management address.
  • Listen port — defaults to 9100.
  • Enable — checkbox.

After enabling, the metric endpoint is live:

Read-only / Safeexporter /metrics
$ curl -s http://192.0.2.1:9100/metrics | head -30
# HELP opnsense_pf_states_total Current number of entries in the PF state table
# TYPE opnsense_pf_states_total gauge
opnsense_pf_states_total 3127
# HELP opnsense_pf_searches_total Total PF table searches
# TYPE opnsense_pf_searches_total counter
opnsense_pf_searches_total 18452394
# HELP opnsense_pf_inserts_total Total PF state insertions
# TYPE opnsense_pf_inserts_total counter
opnsense_pf_inserts_total 189324
# HELP opnsense_pf_removals_total Total PF state removals
# TYPE opnsense_pf_removals_total counter
opnsense_pf_removals_total 186197
# HELP opnsense_pf_state_limit_drops_total Drops due to state table full
# TYPE opnsense_pf_state_limit_drops_total counter
opnsense_pf_state_limit_drops_total 0
# HELP opnsense_interface_bytes_in_total Bytes received per interface
# TYPE opnsense_interface_bytes_in_total counter
opnsense_interface_bytes_in_total{interface="igb0"} 1.84e+09
opnsense_interface_bytes_in_total{interface="igb1"} 3.21e+08

Illustrative output

Configuring the Prometheus scrape

On the Prometheus server, a scrape job is added to prometheus.yml:

scrape_configs:
  - job_name: 'opnsense'
    scrape_interval: 30s
    static_configs:
      - targets: ['192.0.2.1:9100']
        labels:
          device: 'opnsense-fw-01'
          site: 'dc1'

The scrape_interval of 30 seconds is a reasonable balance between resolution and load. The exporter’s metrics do not change between 1-second and 30-second intervals for most use cases; 15 to 60 seconds is typical.

After restarting Prometheus, the target appears in the targets UI. The operator should confirm:

  • The state is UP.
  • The scrape duration is under 1 second.
  • No scrape_error samples are present.

Label cardinality — the trap

Prometheus stores each time series as a unique combination of metric name and labels. A metric with no labels has one time series per scrape; a metric with three labels with five values each has 125 time series. A metric with a label whose value is unbounded — the source IP of every state, the destination port of every flow — can produce millions of time series. The Prometheus server can technically handle millions, but the operational cost becomes untenable.

The os-opnsense-exporter is intentionally low-cardinality. The label values it uses are bounded:

  • interface — bounded by the number of interfaces (typically <20).
  • vhid — bounded by the number of CARP groups (typically <10).
  • rule_label — bounded by the number of named rules (typically <100).

The exporter does not expose per-flow, per-IP, or per-port metrics. If the operator needs those, they should be aggregated (top-N destinations, top-N source IPs) before being exposed, not raw per-flow counters.

The relationship to PF, sysctl, and vmstat

The exporter’s metrics are a thin layer over FreeBSD and PF sources:

  • PF state table → pfctl -s info
  • Interface counters → netstat -I <iface> -b
  • CPU utilisation → vmstat 1 or sysctl kern.cp_time
  • Memory → sysctl hw.physmem, vmstat
  • ARP entries → arp -an | wc -l

The operator who understands the sources can verify the exporter’s output manually. If the exporter reports 3127 state entries, pfctl -s info should report the same number. If it does not, the exporter has a bug or is querying at a different moment.

Read-only / Safeverifying exporter output
$ pfctl -s info | grep 'current entries'; echo '---'; curl -s http://192.0.2.1:9100/metrics | grep opnsense_pf_states_total
current entries                        3127
opnsense_pf_states_total 3127

Illustrative output

Summary

  • The os-opnsense-exporter plugin exposes OPNsense metrics in Prometheus text format on port 9100 by default.
  • The exporter covers PF state, interface counters, system, ARP, DHCP, VPN, Unbound, Suricata, and CARP — bounded, low-cardinality metrics.
  • The Prometheus scrape job is configured with a static target; a 30-second scrape interval is typical.
  • Label cardinality is the trap: per-flow, per-IP, or per-port metrics explode the time-series count and slow the Prometheus server.
  • The exporter’s metrics should be cross-checked against pfctl, netstat, and vmstat for trust.

Knowledge check · 4 questions

  1. Q1. Why does the opnsense-exporter not expose per-flow metrics like opnsense_flow_bytes_total{src_ip=...}?

  2. Q2. The exporter is safe to leave running on a production firewall because it is read-only and does not modify firewall state.

  3. Q3. Which of the following does the os-opnsense-exporter plugin expose? Select all that apply.

  4. Q4. The exporter reports opnsense_pf_states_total of 3000 while pfctl -s info reports current entries of 3200. What is the most likely explanation?

Passing score: 75%. Answers are checked in this browser.