Skip to main content
RunBook Academy

OPNsenseXXXIV · Monitoring and Observability IntegrationGrafana dashboards

Grafana dashboards for OPNsense — from raw metrics to operator insight

Intermediate⏱ ~14 mingrafanaprometheuscurl

What you'll learn

  • Configure a Grafana data source pointing at the Prometheus instance
  • Import the community OPNsense dashboard and adapt it to the deployment
  • Identify the panels that an on-call operator must have at the top of the dashboard
  • Write alert rules for state table occupancy, CPU saturation, and interface drops

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.

Prometheus stores the metrics. Grafana turns them into pictures an operator can read at 03:00. The difference between a dashboard an operator uses and a dashboard an operator ignores is whether the panels answer the operator’s actual questions in the order the operator asks them. A dashboard with 60 panels and no prioritisation is decoration; a dashboard with 6 panels in the right order is a triage tool.

This lesson covers the Prometheus data source, the community OPNsense dashboards, the panels that belong at the top, and the alert rules that turn thresholds into pages.

The data source

Grafana does not store metrics — it queries Prometheus (or InfluxDB, Elasticsearch, Loki, and others). The first step is a data source:

  1. In Grafana: Connections → Data sources → Add data source → Prometheus.
  2. URL: http://prometheus-server:9090 (the Prometheus server’s address).
  3. Authentication: none for internal Prometheus; basic auth or mTLS for external.
  4. Scrape interval: 30s (matches the Prometheus scrape).
  5. Click “Save & test” — confirm “Data source is working”.

The data source is the plumbing. Once it is in place, every dashboard panel that references the data source works.

The community dashboards

The Grafana community has published several OPNsense dashboards at grafana.com/grafana/dashboards. The most useful ones for an operator are:

  • OPNsense — System Health (community ID varies by version): CPU, memory, state table, load, uptime.
  • OPNsense — Interfaces: per-interface bandwidth, drops, errors.
  • OPNsense — PF Statistics: state table over time, rule match counts.
  • OPNsense — Suricata: alert counts, packet counts, drops.

Importing is straightforward: download the dashboard JSON from Grafana’s website, then in Grafana: Dashboards → New → Import → Upload JSON file. The dashboard will reference the Prometheus data source by name — usually the default Prometheus works.

The panels that matter

The panels an on-call operator must have at the top of the dashboard, in the order the operator will look at them during a triage:

PositionPanelQuestion it answers
1State table occupancy (current value + trend)Is the firewall refusing new flows?
2CPU utilisation + interrupt shareIs the firewall CPU-bound?
3Memory used + swap activityIs memory pressure building?
4Interface drops, errors, bps (per interface)Is an interface saturating?
5Recent firewall log volume (lines/min)Is logging volume unusual?
6CARP state per VHIDIs HA healthy?

The order is deliberate. State table first because it is the canary metric from the previous lesson. CPU and memory next because they describe the firewall as a system. Interface counters because they describe the network. Logs because unusual volume is an early signal of an attack or a misconfiguration. CARP last because it is checked only when HA is involved.

A dashboard with 60 panels that buries these six in the middle is a dashboard nobody will read in an incident.

Read-only / Safequerying Prometheus
$ curl -s 'http://prometheus:9090/api/v1/query?query=opnsense_pf_states_total' | head -20
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"opnsense_pf_states_total","instance":"192.0.2.1:9100","job":"opnsense","device":"opnsense-fw-01","site":"dc1"},"value":[1723411200.123,"3127"]}]}}

Illustrative output

Alert rules

A dashboard without alerts is for after-the-fact investigation. The operator who is paged at 03:00 needs alerts that fire before the problem becomes user-visible. The Prometheus alerting rule format:

groups:
  - name: opnsense
    rules:
      - alert: OPNsenseStateTableNearFull
        expr: opnsense_pf_states_total > 8000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: 'OPNsense state table near full on {{ $labels.device }}'
          description: 'Current entries: {{ $value }} of 10000 limit.'

      - alert: OPNsenseStateTableExhausted
        expr: opnsense_pf_state_limit_drops_total > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: 'OPNsense dropping flows — state table exhausted'
          description: 'Drops detected on {{ $labels.device }}.'

      - alert: OPNsenseHighCPU
        expr: 100 - (avg by (device) (rate(opnsense_cpu_idle_total[5m])) * 100) > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: 'OPNsense CPU above 85% on {{ $labels.device }}'

      - alert: OPNsenseInterfaceDrops
        expr: rate(opnsense_interface_drops_in_total[5m]) > 100
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: 'Interface drops above threshold on {{ $labels.device }} {{ $labels.interface }}'

The for: clause is critical — it prevents the alert from firing on a transient spike. A state table at 95% for 30 seconds during a connection burst is not the same as a state table at 95% for 5 minutes sustained.

Dashboards that survive an incident

The dashboard the operator opens at 03:00 is the one that has been maintained in calm conditions. A dashboard with stale thresholds, broken panel queries, or undefined variables is a dashboard the operator will mistrust and abandon. The disciplines:

  • Test panels with the Prometheus data source, not the example data. The community dashboard may have been built against a different exporter version; queries may break.
  • Set thresholds from the operator’s actual SLOs. A state-table alert at 90% of the default 10000 limit is too high for a busy firewall; tune to the deployment.
  • Document the panels. Each panel should have a description that says what the metric means and why the threshold is what it is. The description is read by the next operator on shift.
  • Use variables. A dashboard with a $device variable that filters by firewall name lets one dashboard serve multiple firewalls.
  • Time ranges matter. Default to “last 6 hours” for triage dashboards and “last 30 days” for trend dashboards.

Summary

  • Grafana is the visual layer on Prometheus — the data source is the plumbing, the dashboards are the triage tool.
  • Community OPNsense dashboards are useful starting points but must be adapted to the deployment.
  • The panels that matter, in triage order: state table, CPU, memory, interface counters, log volume, CARP.
  • Alert rules should fire on metrics that require operator action, not on every metric the exporter exposes.
  • Dashboards must be maintained in calm conditions to be trusted during an incident.

Knowledge check · 4 questions

  1. Q1. Why is the order of panels on a triage dashboard important?

  2. Q2. A Prometheus alert rule with for: 30s will fire as soon as the expression is true for 30 seconds.

  3. Q3. Which of the following should be alerted on, rather than just graphed? Select all that apply.

  4. Q4. The operator has imported a community OPNsense dashboard and several panels are broken. What is the most likely cause?

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