Configuration¶
The OPNsense Exporter follows standard Prometheus ecosystem conventions. It can be configured using command-line flags, environment variables, or a combination of both. Environment variables take the prefix OPNSENSE_EXPORTER_ unless noted otherwise.
The flag tables on this page are generated from the exporter's own flag definitions by make docs, so they always match the binary. The definitions themselves live in
internal/options/ on GitHub.
OPNsense connection¶
These settings control how the exporter connects to the OPNsense API.
| Flag | Env Var | Default | Description |
|---|---|---|---|
--opnsense.address |
OPNSENSE_EXPORTER_OPS_API |
-- | Required. Hostname or IP address of OPNsense API |
--opnsense.api-key |
OPNSENSE_EXPORTER_OPS_API_KEY |
-- | API key to use to connect to OPNsense API. This flag/ENV or the OPS_API_KEY_FILE may be set. |
--opnsense.api-secret |
OPNSENSE_EXPORTER_OPS_API_SECRET |
-- | API secret to use to connect to OPNsense API. This flag/ENV or the OPS_API_SECRET_FILE may be set. |
--opnsense.insecure |
OPNSENSE_EXPORTER_OPS_INSECURE |
false |
Disable TLS certificate verification |
--opnsense.max-concurrent-requests |
OPNSENSE_EXPORTER_OPS_MAX_CONCURRENT_REQUESTS |
16 |
Maximum number of background OPNsense API requests in flight across all scheduled collector polls, including nested sub-requests. Bounds the simultaneous PHP/configd load on the firewall: lower it (e.g. 4-8) to protect a low-power appliance at the cost of queued or longer polls; raise it to let more independent polls progress concurrently on capable hardware. It does not affect /metrics replay. Must be >= 1. |
--opnsense.max-retries |
OPNSENSE_EXPORTER_OPS_MAX_RETRIES |
3 |
Number of attempts for a failed OPNsense API request (transport errors / retryable 5xx). Worst-case block time is --opnsense.timeout x this value. |
--opnsense.protocol |
OPNSENSE_EXPORTER_OPS_PROTOCOL |
-- | Required. Protocol to use to connect to OPNsense API. One of: [http, https] |
--opnsense.timeout |
OPNSENSE_EXPORTER_OPS_TIMEOUT |
15s |
Per-request HTTP timeout for calls to the OPNsense API. Combined with --opnsense.max-retries this bounds one endpoint attempt sequence inside a background collector poll (timeout x retries). Keep that product below --exporter.max-scrape-duration so the poll deadline, rather than a request retry, remains the outer bound. Prometheus scrape_timeout applies only to replaying /metrics. |
Note
--opnsense.api-key / --opnsense.api-secret are not marked required because the
file-based secrets below are an alternative source - but one of the two must be set
for each credential. See Security: File-based secrets.
File-based secrets¶
In containers and orchestrated environments, credentials can be read from files:
| Env Var | Description |
|---|---|
OPS_API_KEY_FILE |
Path to a file containing the API key (first line is read) |
OPS_API_SECRET_FILE |
Path to a file containing the API secret (first line is read) |
Note
These environment variables do not use the OPNSENSE_EXPORTER_ prefix. They are checked first: if a file-based secret is set and non-empty, it takes precedence over the flag/env var value.
Exporter settings¶
| Flag | Env Var | Default | Description |
|---|---|---|---|
--annotations.enabled |
OPNSENSE_EXPORTER_ANNOTATIONS_ENABLED |
false |
Write OPNsense change events (reboots, configuration changes, interface counter resets, upgrades, certificate renewals, feed updates) into Grafana's annotation store so they overlay any dashboard. Off by default: this is the exporter's only outbound write. |
--annotations.extra-tags |
OPNSENSE_EXPORTER_ANNOTATIONS_EXTRA_TAGS |
-- | Extra tag to add to every written annotation (repeatable), e.g. env:prod. Every annotation already carries opnsense-exporter, the event kind and instance: |
--annotations.grafana-url |
OPNSENSE_EXPORTER_ANNOTATIONS_GRAFANA_URL |
-- | Grafana base URL to write annotations to, e.g. https://mystack.grafana.net. |
--annotations.interval |
OPNSENSE_EXPORTER_ANNOTATIONS_INTERVAL |
60s |
How often the watched event metrics are checked for changes. This bounds how late an annotation is WRITTEN, never where it is PLACED — each annotation carries the event's own timestamp. |
--annotations.lookback |
OPNSENSE_EXPORTER_ANNOTATIONS_LOOKBACK |
24h |
How old an event may be and still be worth annotating, and how far back the startup reconciliation looks for annotations this exporter already wrote. Keeps a fresh deployment from annotating a reboot that happened months ago. Read this together with --annotations.max-per-cycle: a fresh deployment finds every event inside this window at once, and that first-run backlog drains at most max-per-cycle annotations per --annotations.interval (default 20/60s), so a 24h lookback on a busy firewall takes several minutes to catch up. Shorten this if you want a fresh deployment to start clean rather than backfill a day. |
--annotations.max-per-cycle |
OPNSENSE_EXPORTER_ANNOTATIONS_MAX_PER_CYCLE |
20 |
Maximum annotation posts ATTEMPTED per check, successful or not. A guard against one bad reading writing hundreds of annotations, not a rate limit to tune. It also paces the first-run backlog --annotations.lookback produces: the excess is not marked seen, so it is re-proposed on the next check and a deployment with a 24h lookback drains at this many per --annotations.interval until it is caught up. Events are only lost if they age out of the lookback before the backlog reaches them. Raising it drains faster but makes a rate limit (opnsense_exporter_annotations_rate_limited_total) more likely, since a Grafana org shares one annotation limit across every writer. |
--annotations.timeout |
OPNSENSE_EXPORTER_ANNOTATIONS_TIMEOUT |
10s |
Timeout for each Grafana annotation API request. |
--annotations.token |
OPNSENSE_EXPORTER_ANNOTATIONS_TOKEN |
-- | Grafana service-account token used to write annotations. It needs the annotation write permission and nothing else. This flag/ENV or OPNSENSE_EXPORTER_ANNOTATIONS_TOKEN_FILE may be set. |
--collector.health-poll-interval |
OPNSENSE_EXPORTER_COLLECTOR_HEALTH_POLL_INTERVAL |
60s |
Interval at which the exporter polls the OPNsense health endpoint (#386). This is the circuit-breaker cadence: the health poll sets and clears the process-wide 'firewall unreachable' flag, so it bounds how quickly collectors resume after the box recovers. Independent of --collector.poll-interval since #386, which previously controlled it by accident. Clamped to [5s, 15m]. |
--collector.poll-interval |
OPNSENSE_EXPORTER_COLLECTOR_POLL_INTERVAL |
60s |
Default interval at which each collector polls the OPNsense API into the in-memory snapshot that /metrics and the OTLP bridge replay (#336). A collector may declare its own faster/slower tier; every interval is clamped to [5s, 15m]. |
--collector.poll-interval-override |
OPNSENSE_EXPORTER_COLLECTOR_POLL_INTERVAL_OVERRIDE |
-- | Override a specific collector's poll interval as |
--config.check |
-- | -- | Validate the effective configuration and exit, without binding any port, starting the poll scheduler, contacting OPNsense, or exporting telemetry. Exits 0 when the configuration is usable and 1 otherwise. Referenced files (API key/secret, TLS keypairs) are read; network reachability is deliberately not checked (that is what /-/ready is for). Has no env var by design: an ambient one would turn every start into a no-op. |
--exporter.cache-ttl |
OPNSENSE_EXPORTER_CACHE_TTL |
1h0m0s |
How long to cache responses from slow-moving API endpoints (system/CPU identity, certificate inventory, Unbound DNS blocklist policy config) and to remember that a plugin-gated endpoint is absent (its 404). This data changes only on an admin action - a config edit, a certificate renewal, a plugin install - so re-fetching it on every poll only costs firewall CPU. Set it above the collector poll interval or it can never serve a hit. The cost is staleness: a newly installed plugin, or a cert change, can take up to this long to show up. Set to 0 to fetch everything on every poll. Live data (counters, rates, service run-state) is never cached regardless of this setting. |
--exporter.firmware-cache-ttl |
OPNSENSE_EXPORTER_FIRMWARE_CACHE_TTL |
12h0m0s |
How long to cache firmware API responses (status and, when enabled, package details). The firmware data OPNsense serves is the stored result of the box's own update check, which it refreshes roughly daily, so re-fetching it on every poll only costs firewall CPU. Set to 0 to fetch on every poll. |
--exporter.ids-alert-lookback |
OPNSENSE_EXPORTER_IDS_ALERT_LOOKBACK |
15m |
Lookback window over which opnsense_ids_recent_alerts counts Suricata eve alerts (a gauge). Only used when --exporter.enable-ids-alerts is set. Counts are a floor when more than 500 alerts fall inside the window. |
--exporter.instance-label |
OPNSENSE_EXPORTER_INSTANCE_LABEL |
-- | Label to use to identify the instance in every metric. If you have multiple instances of the exporter, you can differentiate them by using different value in this flag, that represents the instance of the target OPNsense. If left empty, it defaults to the configured OPNsense address (deterministic). Set --exporter.instance-use-hostname to derive it from the OPNsense hostname instead. |
--exporter.instance-use-hostname |
OPNSENSE_EXPORTER_INSTANCE_USE_HOSTNAME |
false |
When --exporter.instance-label is empty, derive the instance label from the OPNsense hostname reported by the API instead of the configured address. This lookup is deterministic: it blocks at startup and, if the hostname cannot be obtained, the exporter refuses to start (rather than silently falling back to the address, which would make the label depend on startup timing and flip between restarts). |
--exporter.max-scrape-duration |
OPNSENSE_EXPORTER_MAX_SCRAPE_DURATION |
50s |
Upper bound on a single collector poll (#336). Since serving /metrics now replays an in-memory snapshot rather than calling the API, this bounds each background poll so a stalled/blackholed endpoint frees its poll-concurrency slot instead of holding it open. Serving itself is never blocked by it. |
--exporter.series-budget |
OPNSENSE_EXPORTER_SERIES_BUDGET |
100000 |
Soft budget for the total number of Prometheus series produced by the COLLECTOR registry (the same set /metrics and the OTLP bridge serve, and what metricsnap replays to the web UI's /cardinality report) — this is NOT the exporter process's full series count: process_/go_ self-metrics and the opnsense_exporter_otlp_* delivery-health family live on a separate self registry and are never counted here, so this number will read lower than what your Prometheus tenant ultimately stores for this job. Nothing is ever dropped, capped or refused when it is exceeded (#494) — exceeding it only logs a rate-limited warning (once on the transition into the over-budget state, then at most hourly while it persists, and once more on the transition back under budget) and is reported on /cardinality alongside the existing per-metric warn/crit thresholds, which are a different, unrelated dimension. Set to 0 to disable the check entirely. |
--flow.correlate |
OPNSENSE_EXPORTER_FLOW_CORRELATE |
true |
Correlate NetFlow fragments and Zenarmor conn documents into one merged flow record per connection-window. A pass-through when only one source is present. Off emits NetFlow records raw and per-fragment. |
--flow.correlate.max-entries |
OPNSENSE_EXPORTER_FLOW_CORRELATE_MAX_ENTRIES |
50000 |
Hard cap on live correlator entries. At the cap the oldest is force-emitted (never dropped) and counted. The NetFlow ingress is unauthenticated, so this bounds memory against a flood. 0 is unbounded (unwise with the listener on). |
--flow.correlate.window |
OPNSENSE_EXPORTER_FLOW_CORRELATE_WINDOW |
3m |
How long the correlator holds a connection-window before emitting. Also the maximum a flow log is delayed. NetFlow export lag runs to ~30m for long flows (#346), so a flow whose records straddle the window emits a partial per window rather than one joined record. |
--flow.dns-cache.size |
OPNSENSE_EXPORTER_FLOW_DNS_CACHE_SIZE |
50000 |
Entries in the DNS answer cache that gives a flow to a bare IP its dst.domain, fed by the Zenarmor dns family. Over the cap it stops inserting rather than evicting hot entries. 0 disables domain enrichment. |
--flow.enabled |
OPNSENSE_EXPORTER_FLOW_ENABLED |
true |
Enable flow rollups: bounded byte and packet volume counters derived from flow records. Costs nothing where no flow source is configured - the metrics are simply silent, like log_events without the syslog receiver. Set --exporter.disable-flow to remove the collector entirely. |
--flow.geoip.metric-dims |
OPNSENSE_EXPORTER_FLOW_GEOIP_METRIC_DIMS |
false |
Add a country label to the flow volume metrics. OFF by default and it should usually stay off: country is a ~250-value dimension multiplied against every existing flow series, so turning it on can multiply opnsense_flow_bytes_total's cardinality roughly 250-fold. --flow.top-n and --flow.max-keys still bound the result, which means the practical effect on a busy box is that real series start folding into other rather than that the family grows without limit. ASN and city NEVER become labels at any setting. Geo on flow LOGS needs no flag - it is unconditional whenever --geoip.enabled is set. |
--flow.log-mode |
OPNSENSE_EXPORTER_FLOW_LOG_MODE |
per_flow |
Flow log emission: "per_flow" ships one OTLP log record per correlated flow on the shared log pipeline; "off" ships none while still deriving all metrics. Zenarmor conn documents ship on their own lane regardless. |
--flow.max-keys |
OPNSENSE_EXPORTER_FLOW_MAX_KEYS |
2500 |
Maximum distinct label combinations the flow accumulator tracks in memory. A separate bound from --flow.top-n: this caps memory between scrapes, that caps emitted series. Combinations first seen at the cap fold into other and are counted by opnsense_flow_rollup_capped_total. 0 is unbounded. |
--flow.max-logs-per-window |
OPNSENSE_EXPORTER_FLOW_MAX_LOGS_PER_WINDOW |
0 |
Cap on flow log records shipped per minute; excess is TRUNCATED (never sampled) and counted. A flood guard on the unauthenticated NetFlow ingress. 0 is unlimited. Metrics are never truncated. |
--flow.netflow.allowed-peers |
OPNSENSE_EXPORTER_FLOW_NETFLOW_ALLOWED_PEERS |
-- | CIDR allowlist of exporters permitted to send flow records, repeatable. Empty means accept from anyone, which is a deliberate decision to trust the network rather than a default to drift into: anything that can reach the port can inject flow records. |
--flow.netflow.debug-capture |
OPNSENSE_EXPORTER_FLOW_NETFLOW_DEBUG_CAPTURE |
off |
Dump raw NetFlow datagrams to --logs.debug-capture.dir. "unidentified" writes only datagrams carrying something the decoder could not interpret (an unmodelled template element, an options template, an unknown flowset, or a datagram that would not decode at all) - cheap, and the mode worth leaving on. "all" writes every datagram, for regenerating a replay fixture or measuring the export; deliberately heavy, bounded only by --logs.debug-capture.max-bytes. Requires --flow.netflow.enabled and the shared dir. |
--flow.netflow.enabled |
OPNSENSE_EXPORTER_FLOW_NETFLOW_ENABLED |
false |
Enable the NetFlow v5/v9 receiver. Opens an UNAUTHENTICATED UDP socket: NetFlow has no authentication of any kind, so restrict it with --flow.netflow.allowed-peers or by firewalling the port. Requires --flow.enabled. |
--flow.netflow.ifindex-map |
OPNSENSE_EXPORTER_FLOW_NETFLOW_IFINDEX_MAP |
-- | Override the derived NetFlow ifIndex-to-device map, as comma-separated index=device pairs (e.g. "1=ixl0,5=igb0,13=ixl0_vlan50"). Entries listed here beat the derived map; indices not listed still use it, so pin every index that carries traffic. Read yours off the box with: ifinfo | awk '$1 == "Interface" { n++; print n, $2 }' - that is the whole enumeration. ngctl list | grep netflow shows only the interfaces netflow captures, and an egress index can legitimately name one it does not. A pin is a STATIC assertion against a POSITIONAL index: adding or removing any interface renumbers every position above it, so a pin that was right when written silently goes stale and then actively mislabels, because it still wins. Re-read the enumeration after any interface change and watch opnsense_flow_ifindex_conflicts, whose reason="derived_differs" is that divergence; settle which side is right with ngctl show netflow_ |
--flow.netflow.listen |
OPNSENSE_EXPORTER_FLOW_NETFLOW_LISTEN |
:2055 |
Address the NetFlow receiver binds, host:port. Bound eagerly at startup, so a port already in use is a startup error rather than a receiver that is silently never there. |
--flow.top-n |
OPNSENSE_EXPORTER_FLOW_TOP_N |
1000 |
Maximum flow series emitted per scrape. Everything beyond folds into a single other series per source, so the family still sums exactly at any limit. 0 emits every tracked combination. |
--flow.top-talkers |
OPNSENSE_EXPORTER_FLOW_TOP_TALKERS |
false |
Emit opnsense_flow_top_talker_bytes_total: bytes per internal host and direction, top-N with an other remainder. OFF by default because the host label is high cardinality; the top-N bounds it but a host label is still one series per host. |
--flow.zenarmor |
OPNSENSE_EXPORTER_FLOW_ZENARMOR |
true |
Derive flow records from the Zenarmor receiver's conn documents. Adds no new log records to Loki: the conn document ships exactly as before and this only feeds the metric rollup. Requires --logs.zenarmor.enabled to produce anything. |
--geoip.asn-database |
OPNSENSE_EXPORTER_GEOIP_ASN_DATABASE |
-- | Path to a MaxMind GeoLite2-ASN / GeoIP2-ISP database. This is the one enrichment no amount of Zenarmor coverage supplies: Zenarmor ships no ASN database on any box. Defaults to the downloaded copy when --geoip.download.enabled is set. |
--geoip.country-database |
OPNSENSE_EXPORTER_GEOIP_COUNTRY_DATABASE |
-- | Path to a MaxMind Country OR City database (GeoLite2-Country, GeoLite2-City, GeoIP2-City). A City database is a strict superset, so one path accepts either and the city/region attributes are simply absent with a Country file. Defaults to the downloaded copy when --geoip.download.enabled is set. A missing file is not an error - enrichment is fail-open and the attributes are just absent. |
--geoip.download.account-id |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_ACCOUNT_ID |
-- | MaxMind account ID for the database download API (the Basic-auth username). |
--geoip.download.dir |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_DIR |
/var/lib/opnsense-exporter/geoip |
Directory downloaded databases are installed into, as |
--geoip.download.editions |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_EDITIONS |
GeoLite2-Country,GeoLite2-ASN |
Comma-separated MaxMind edition IDs to download. Default is Country + ASN (~9 MB and ~12 MB resident). Swap GeoLite2-Country for GeoLite2-City (~60 MB resident) to get city and region attributes without Zenarmor - the same --geoip.country-database path accepts either edition. |
--geoip.download.enabled |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_ENABLED |
false |
Download MaxMind databases directly, so no geoipupdate cron or sidecar is needed. Requires --geoip.download.account-id and a license key. Conditional requests mean an unchanged database costs a 304 and no download quota. Off by default: operator-managed files are the supported baseline and this adds an outbound network dependency. |
--geoip.download.interval |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_INTERVAL |
24h |
How often to ask MaxMind for a newer build. GeoLite2 rebuilds twice a week, so daily is ample; an unchanged database answers 304 and costs no quota. The first download runs at startup regardless, so a fresh container is not blind for a whole interval. 0 downloads only at startup. |
--geoip.download.license-key |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_LICENSE_KEY |
-- | MaxMind license key. This flag/ENV or OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_LICENSE_KEY_FILE may be set; the file form is preferred for a container secret. |
--geoip.download.timeout |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_TIMEOUT |
5m |
End-to-end timeout for one edition's download. A timeout leaves the installed database untouched and is retried on the next interval. |
--geoip.enabled |
OPNSENSE_EXPORTER_GEOIP_ENABLED |
false |
Enable local GeoIP enrichment of flow records from MaxMind .mmdb files on disk. Adds country/continent/city/ASN attributes to flow LOGS for external addresses, so geo no longer depends on whether Zenarmor happened to see the connection. Purely local: no lookup ever touches the network. Off by default because it needs a database the exporter does not ship. |
--geoip.reload-interval |
OPNSENSE_EXPORTER_GEOIP_RELOAD_INTERVAL |
15m |
How often to re-stat the database paths and hot-swap a changed file. This is what makes the operator-managed path work - a geoipupdate cron, a sidecar or a re-mounted volume can rewrite the files under a running exporter. Separate from --geoip.download.interval, which asks MaxMind whether a newer build exists. 0 disables reloading. |
--log.format |
-- | logfmt |
Output format of log messages. One of: [logfmt, json] |
--log.level |
-- | info |
Only log messages with the given severity or above. One of: [debug, info, warn, error] |
--logs.batch-max |
OPNSENSE_EXPORTER_LOGS_BATCH_MAX |
5000 |
Maximum number of records the emitter hands to the sink per batch. The sink pays a fixed per-resource-partition round-trip, and distinct partitions plateau with batch duration, so a larger batch amortises that fixed cost almost linearly rather than costing proportionally more. |
--logs.buffer-max-bytes |
OPNSENSE_EXPORTER_LOGS_BUFFER_MAX_BYTES |
134217728 |
Aggregate byte budget for the in-memory backpressure queue. The record-count cap (--logs.buffer-size) alone does not bound memory: a receiver preserves each record's raw body, so a few large records can outweigh thousands of small ones. On overflow the oldest record is dropped and counted, exactly as for the count cap. 0 disables the byte budget. |
--logs.buffer-size |
OPNSENSE_EXPORTER_LOGS_BUFFER_SIZE |
65536 |
Capacity of the in-memory backpressure queue between pollers and the sink. On overflow the oldest record is dropped and counted (logs_dropped_total). At the measured ~475 bytes/record retained size, 65536 records is ~31MB, comfortably under the 128MiB --logs.buffer-max-bytes default, so the two bounds read against one number instead of this record cap silently binding first at a fraction of the byte budget. |
--logs.crowdsec.enabled |
OPNSENSE_EXPORTER_LOGS_CROWDSEC_ENABLED |
false |
Enable the crowdsec log source: ships CrowdSec alert and decision records to Loki (there is no native syslog path for these - the plugin registers no syslog scope; alerts live only in the LAPI). Requires --logs.enabled. Polls at a 60s floor regardless of --logs.poll-interval. Silent when the os-crowdsec plugin is absent. Off by default. |
--logs.debug-capture.dir |
OPNSENSE_EXPORTER_LOGS_DEBUG_CAPTURE_DIR |
-- | Directory to dump UNMODELLED receiver signals into for inspection, as NDJSON under |
--logs.debug-capture.max-bytes |
OPNSENSE_EXPORTER_LOGS_DEBUG_CAPTURE_MAX_BYTES |
256MiB |
Total size cap for --logs.debug-capture.dir (e.g. 256MiB, 1GB). Capture STOPS when the dir reaches this, keeping the oldest samples; it never deletes to make room, so a debug capture can never fill the disk. Counts bytes left by previous runs. |
--logs.enabled |
OPNSENSE_EXPORTER_LOGS_ENABLED |
false |
Enable the opt-in log/event shipping pipeline (polls OPNsense event APIs and ships to Loki via OTLP). Off by default. Independent of --otlp.enabled (which gates metrics). |
--logs.ids.enabled |
OPNSENSE_EXPORTER_LOGS_IDS_ENABLED |
false |
Enable the IDS (Suricata EVE alert) log source: ships full Suricata alert records polled via ids/service/query_alerts. Off by default. Requires --logs.enabled. If the box already forwards EVE JSON via syslog (ids.general.syslog_eve), prefer that native path instead of also enabling this source - do not ship the same alerts twice. |
--logs.max-metric-keys |
OPNSENSE_EXPORTER_LOGS_MAX_METRIC_KEYS |
5000 |
Maximum distinct label tuples retained per derived log_events metric family. Receivers are push-based and syslog over UDP has a spoofable source, so tuple values are sender-controlled: without this bound a sender can grow process-lifetime metric state without limit. Tuples beyond the cap fold into a counted overflow series rather than being dropped silently. 0 disables the cap. |
--logs.max-record-bytes |
OPNSENSE_EXPORTER_LOGS_MAX_RECORD_BYTES |
1048576 |
Maximum estimated retained size for a single record - its body, source and attributes plus a fixed overhead allowance, measured the same way as --logs.buffer-max-bytes so the two read against one number. A record larger than this is rejected at ingest and counted rather than queued, so one oversized record cannot occupy the whole queue budget or become a batch the sink permanently refuses. 0 disables the per-record cap. |
--logs.poll-interval |
OPNSENSE_EXPORTER_LOGS_POLL_INTERVAL |
10s |
Base interval between event polls per source (floor 5s). Sources may raise their own floor. |
--logs.ship-concurrency |
OPNSENSE_EXPORTER_LOGS_SHIP_CONCURRENCY |
8 |
Maximum number of resource partitions within one batch that the sink exports concurrently. Each partition is a separate synchronous wire request, so a batch of N partitions previously cost N sequential round-trips. 1 restores the old fully-sequential behaviour. Values below 1 are normalised to 1. |
--logs.ship-max-attempts |
OPNSENSE_EXPORTER_LOGS_SHIP_MAX_ATTEMPTS |
10 |
Maximum delivery attempts for one batch before it is dropped and counted (logs_dropped_total{reason="ship_failed_permanent"}). Retries are exponentially backed off. Without this bound a batch the sink permanently refuses is retried forever by the single emitter goroutine, wedging all subsequent delivery. 0 restores unlimited retries. |
--logs.sink |
OPNSENSE_EXPORTER_LOGS_SINK |
otlp |
Log shipping sink: otlp (OTLP logs, reuses the --otlp.* transport) or stdout (one JSON line per event). |
--logs.state-file |
OPNSENSE_EXPORTER_LOGS_STATE_FILE |
-- | Optional path to persist per-source cursors across restarts (atomic JSON). Empty = in-memory only (resume from now on restart). |
--logs.syslog.allowed-peers |
OPNSENSE_EXPORTER_LOGS_SYSLOG_ALLOWED_PEERS |
-- | Comma-separated CIDR allowlist of hosts permitted to send syslog (e.g. 10.0.0.254/32). Empty accepts any sender. Syslog is unauthenticated, so set this on a shared network. |
--logs.syslog.debug-capture |
OPNSENSE_EXPORTER_LOGS_SYSLOG_DEBUG_CAPTURE |
false |
Dump syslog lines this receiver cannot parse (unknown program, no matching parser, or an unparseable envelope) to --logs.debug-capture.dir for inspection. Requires --logs.debug-capture.dir. Additive - these lines still ship as generic records. |
--logs.syslog.enabled |
OPNSENSE_EXPORTER_LOGS_SYSLOG_ENABLED |
false |
Enable the syslog receiver: listens for logs pushed by OPNsense (RFC5424 or RFC3164, UDP and/or TCP) and ships them enriched with rule descriptions, interface names and hostnames. Off by default. Requires --logs.enabled. Configure a matching target on the firewall under System > Settings > Logging > Targets. |
--logs.syslog.enrich |
OPNSENSE_EXPORTER_LOGS_SYSLOG_ENRICH |
true |
Enrich received syslog records from the OPNsense API: firewall rule descriptions (including auto-generated system rules), friendly interface names, DHCP hostnames, MAC addresses, local/remote scope and well-known service names. |
--logs.syslog.exclude-programs |
OPNSENSE_EXPORTER_LOGS_SYSLOG_EXCLUDE_PROGRAMS |
-- | Comma-separated syslog programs to DROP (e.g. radvd,cron). Empty ships everything. Dropped records are counted in opnsense_exporter_logs_rejected_total{reason="filtered"} - never silently discarded. |
--logs.syslog.include-programs |
OPNSENSE_EXPORTER_LOGS_SYSLOG_INCLUDE_PROGRAMS |
-- | Comma-separated syslog programs to ship, dropping everything else. Empty ships everything. Mutually exclusive with --logs.syslog.exclude-programs. |
--logs.syslog.listen-tcp |
OPNSENSE_EXPORTER_LOGS_SYSLOG_LISTEN_TCP |
:5514 |
TCP listen address for the syslog receiver. Empty disables the TCP listener. Prefer TCP for firewall logs: UDP datagram loss is silent and unrecoverable. |
--logs.syslog.listen-tls |
OPNSENSE_EXPORTER_LOGS_SYSLOG_LISTEN_TLS |
-- | TLS listen address for the syslog receiver (RFC5424 over TLS, OPNsense tls4/tls6). Empty disables the TLS listener. Requires --logs.syslog.tls-cert-file and --logs.syslog.tls-key-file. |
--logs.syslog.listen-udp |
OPNSENSE_EXPORTER_LOGS_SYSLOG_LISTEN_UDP |
:5514 |
UDP listen address for the syslog receiver. Empty disables the UDP listener. Port 5514 (not 514) because 514 is privileged and the container runs non-root. |
--logs.syslog.max-conns |
OPNSENSE_EXPORTER_LOGS_SYSLOG_MAX_CONNS |
64 |
Maximum concurrent connections to the syslog receiver, applied PER TRANSPORT: plain TCP and TLS each get this budget from a separate pool. They are separate so a plaintext flood cannot starve authenticated mTLS senders out of the capacity they need. Bounds goroutine growth on an unauthenticated ingress; with both transports enabled the worst-case connection count is twice this value. |
--logs.syslog.min-severity |
OPNSENSE_EXPORTER_LOGS_SYSLOG_MIN_SEVERITY |
-- | Drop records less severe than this (emerg, alert, crit, err, warning, notice, info, debug). E.g. notice drops info and debug. Empty ships every severity. |
--logs.syslog.sample |
OPNSENSE_EXPORTER_LOGS_SYSLOG_SAMPLE |
false |
Sample (drop) high-volume raw log lines AFTER their metrics have been derived: keep firewall block/reject lines and drop passes, keep HAProxy state changes and errors and drop the per-connection noise. Low-volume programs (sshd, dhcp, audit, ids) are kept in full. Off by default. Requires the log_events collector (exporter.disable-log-events must not be set) so every dropped line is counted first. |
--logs.syslog.sampled-attribute |
OPNSENSE_EXPORTER_LOGS_SYSLOG_SAMPLED_ATTRIBUTE |
true |
When sampling is on, stamp a sampled="true" attribute on every shipped line so consumers know the log stream is incomplete and must use the derived counters for totals. On by default; only takes effect when --logs.syslog.sample is set. |
--logs.syslog.tls-cert-file |
OPNSENSE_EXPORTER_LOGS_SYSLOG_TLS_CERT_FILE |
-- | PEM server certificate for the TLS syslog listener. |
--logs.syslog.tls-client-ca-file |
OPNSENSE_EXPORTER_LOGS_SYSLOG_TLS_CLIENT_CA_FILE |
-- | PEM CA bundle to verify sender client certificates on the TLS syslog listener. When set, a sender MUST present a certificate signed by this CA - the only real sender authentication syslog offers. Empty accepts any TLS client (encryption only). |
--logs.syslog.tls-key-file |
OPNSENSE_EXPORTER_LOGS_SYSLOG_TLS_KEY_FILE |
-- | PEM private key for the TLS syslog listener. |
--logs.unbound.enabled |
OPNSENSE_EXPORTER_LOGS_UNBOUND_ENABLED |
false |
Enable the opt-in Unbound per-query DNS log source (pi-hole-style query log to Loki: domain, client, action, resolution source, blocklist and dnssec_status per query). Off by default; requires --logs.enabled. CAVEAT: without a per-client filter, Unbound's query-log backend (DuckDB) only ever exposes the newest 1000 rows across the WHOLE resolver - on a firewall sustaining more than roughly 1000 queries between polls, older rows silently fall out of that window before this exporter ever sees them. This is accepted, honestly-counted sampling loss, not a bug: it is tracked via opnsense_exporter_logs_possible_gap_total{source="unbound"}, never silently dropped. Homelab/SMB query volumes are fine; a busy enterprise resolver should not enable this. Also requires Unbound reporting/statistics enabled on the firewall. Poll floor 15s regardless of --logs.poll-interval. |
--logs.zenarmor.allowed-peers |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_ALLOWED_PEERS |
-- | Comma-separated CIDR allowlist of hosts permitted to stream (e.g. 10.0.0.254/32). Empty accepts any sender. The receiver is unauthenticated unless --logs.zenarmor.auth-user is set, so set this on a shared network. |
--logs.zenarmor.auth-password |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_AUTH_PASSWORD |
-- | Password for --logs.zenarmor.auth-user. |
--logs.zenarmor.auth-user |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_AUTH_USER |
-- | Require HTTP basic auth on the Zenarmor receiver, with this username. Set the same credentials in Zenarmor's streaming settings. Empty disables auth. |
--logs.zenarmor.debug-capture |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_DEBUG_CAPTURE |
false |
Dump Zenarmor signals this receiver does not model (unhandled Elasticsearch endpoints, unknown families, documents that would not parse) to --logs.debug-capture.dir for inspection. Requires --logs.debug-capture.dir. While on, the unhandled-endpoint warning is suppressed - the capture file carries the same signal. |
--logs.zenarmor.drop-self-traffic |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_DROP_SELF_TRAFFIC |
true |
Drop records describing the exporter's own Elasticsearch ingest connection - Zenarmor inspects the link the receiver listens on, so it reports the very connection delivering its records (roughly 15% of all volume, and most of the http family). Matched on the streaming peer's address plus the receiver's listen port, never the destination address, which a containerised exporter cannot know. Set false to keep them; drops are counted as logs_rejected_total{reason="self_traffic"}. |
--logs.zenarmor.enabled |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_ENABLED |
false |
Enable the Zenarmor receiver: poses as an Elasticsearch node so Zenarmor can stream its reporting data (connections, DNS, TLS, HTTP, threat alerts) to the exporter, which ships it enriched over OTLP. Off by default. Requires --logs.enabled. Configure the firewall under Configuration/Zenarmor > Settings > Streaming Data > 'Stream Reporting Data to External Elasticsearch' - NOT the initial wizard's 'Remote Elasticsearch Database', which replaces local reporting irreversibly. |
--logs.zenarmor.enrich |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_ENRICH |
true |
Enrich received Zenarmor records from the OPNsense API: friendly interface names, local/remote scope and well-known service names. Zenarmor resolves hostnames, MACs and device identity itself, so this adds only what it does not already know. |
--logs.zenarmor.exclude |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_EXCLUDE |
-- | Drop Zenarmor records whose FIELD matches REGEX, as FIELD=~REGEX (e.g. 'server_name=~.*.grafana.net'). Repeatable; default off. The field name is validated at startup against the receiver's attribute vocabulary - a typo is a startup error, never a silent no-op. Derived counters are observed BEFORE the drop, so opnsense_log_events_zenarmor_total stays complete; drops are counted as logs_rejected_total{reason="excluded"} and logs_zenarmor_excluded_total{rule}. EXCLUSION IS LOSSY: the derived counters carry no server_name, query or device_name, so an excluded record's forensic detail is gone for good. Prefer a query-time filter unless volume genuinely forces this. Set via env as one rule per LINE. |
--logs.zenarmor.families |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_FAMILIES |
-- | Comma-separated Zenarmor families to ship (conn, dns, tls, http, alert, sip). Empty ships all of them. Prefer restricting this at the Zenarmor end instead - data cut at source never crosses the wire. Zenarmor streams ~2.5-3.3M records/day (~4-6 GB/day of JSON), of which conn is ~61%. |
--logs.zenarmor.listen-http |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_LISTEN_HTTP |
:9200 |
Listen address for the Zenarmor receiver. Point Zenarmor's streaming URI at it. |
--logs.zenarmor.max-concurrent-requests |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_MAX_CONCURRENT_REQUESTS |
8 |
Maximum bulk requests processed concurrently by the Zenarmor receiver. The per-request body limit bounds one request; without this, N simultaneous requests each buffer that full allowance. Excess requests are refused with 503 before a body is read. 0 disables the limit. |
--logs.zenarmor.tls-cert-file |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_TLS_CERT_FILE |
-- | PEM server certificate for the Zenarmor receiver. Set with --logs.zenarmor.tls-key-file to serve HTTPS, and use an https:// URI in Zenarmor's streaming settings. |
--logs.zenarmor.tls-key-file |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_TLS_KEY_FILE |
-- | PEM private key for --logs.zenarmor.tls-cert-file. |
--logs.zenarmor.transport |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_TRANSPORT |
elasticsearch |
How Zenarmor delivers its reporting data: 'elasticsearch' (default) runs the built-in Elasticsearch receiver on --logs.zenarmor.listen-http; 'syslog' ingests it through the shared syslog receiver (requires --logs.syslog.enabled and a business-tier Zenarmor licence). families/exclude/enrich/drop-self-traffic apply to either transport. |
--web.config.file |
-- | -- | Path to configuration file that can enable TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md |
--web.disable-exporter-metrics |
OPNSENSE_EXPORTER_DISABLE_EXPORTER_METRICS |
-- | Exclude metrics about the exporter itself (process_, go_). |
--web.listen-address |
-- | :8080 |
Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. Examples: :9100 or [::1]:9100 for http, vsock://:9100 for vsock |
--web.systemd-socket |
-- | -- | Use systemd socket activation listeners instead of port listeners (Linux only). |
--web.telemetry-path |
OPNSENSE_EXPORTER_WEB_TELEMETRY_PATH |
/metrics |
Path under which to expose metrics. |
--web.ui-disable-config |
OPNSENSE_EXPORTER_WEB_UI_DISABLE_CONFIG |
false |
Hide the /config page. |
--web.ui-disable-devices |
OPNSENSE_EXPORTER_WEB_UI_DISABLE_DEVICES |
false |
Hide the /devices page (exposes MAC/hostname). |
--web.ui-enabled |
OPNSENSE_EXPORTER_WEB_UI_ENABLED |
true |
Serve the operator console at / (else the minimal landing page). |
--web.ui-refresh-interval |
OPNSENSE_EXPORTER_WEB_UI_REFRESH_INTERVAL |
5s |
Live-poll interval for the console's dynamic pages. |
Poll intervals and the response cache¶
Two different caches sit between the firewall and a scrape, and they do not overlap:
- The poll snapshot removes API work from the scrape path. Each collector polls on its own interval (
--collector.poll-interval, its built-in tier, or a--collector.poll-interval-override) into an in-memory snapshot;/metricsand the OTLP bridge replay that snapshot and never call the firewall. Scrape as often as you like - it costs the box nothing. - The response cache (
--exporter.cache-ttl,--exporter.firmware-cache-ttl) removes API work from the poll path, for the handful of endpoints whose payload only changes on an admin action. A collector on the 15-minute tier would otherwise ask the box four times an hour for a certificate inventory that changes once a quarter.
Because the response cache is consumed by polls, set its TTL longer than the poll interval of the collectors that use it - a TTL below the poll interval can never serve a hit and just adds a lookup. Poll intervals are clamped to a 15-minute ceiling, so both defaults (1h and 12h) are above the slowest possible poll. Setting either to 0 disables that cache and sends every poll to the firewall.
A plugin-gated endpoint's 404 is remembered separately, under the same --exporter.cache-ttl. That is a fact about the route (the plugin is not installed), not about payload freshness, so it applies regardless of how a collector polls.
Health endpoints & scrape filtering¶
The exporter serves two probe endpoints alongside /metrics:
| Path | Behavior |
|---|---|
/-/healthy |
Liveness: always 200 OK while the process is serving. No upstream dependency. |
/-/ready |
Readiness: 200 OK when the OPNsense API health check succeeds and the poll scheduler has warmed up (every enabled collector has completed its first poll), 503 otherwise. Results (including failures) are cached for 10 seconds so Kubernetes probes cannot hammer the firewall API; each upstream probe is bounded to 5 seconds and detached from the prober's own request timeout. |
Readiness covers warm-up, not just reachability
Collectors poll on their own intervals into an in-memory snapshot that /metrics replays, so a freshly started exporter serves a partial metric set until every collector has polled once - typically a few tens of seconds, bounded by the startup jitter and the poll-concurrency cap. /-/ready stays 503 for that window, which makes it the right gate for ordered startup and for any script that asserts against a complete scrape. A failed first poll still counts as warmed up (it is reported by opnsense_exporter_scrape_collector_success=0), so one broken plugin cannot hold readiness open indefinitely.
Kubernetes: do not gate readiness on the firewall
/-/ready depends on the OPNsense API. If Prometheus discovers the exporter via Kubernetes Service endpoints, a not-ready pod drops out of the endpoints list - so an unreachable firewall would stop the exporter being scraped and you would lose the opnsense_up=0 signal exactly when the firewall is down. Do not use /-/ready as a readinessProbe in that setup - use /-/healthy for both probes (as the bundled deploy/k8s/deployment.yaml does). /-/ready is intended for ordered startup and manual/external checks.
Note: if you configure basic_auth_users in the exporter-toolkit web config file (--web.config.file), authentication applies to all endpoints including /-/healthy and /-/ready - Kubernetes probes cannot easily send basic-auth credentials, so prefer network-level protection over basic auth when probes are in use.
/metrics supports node_exporter-style per-scrape collector filtering:
curl 'http://localhost:8080/metrics?collect[]=gateways&collect[]=interfaces'
curl 'http://localhost:8080/metrics?exclude[]=firewall_rule'
collect[] and exclude[] are mutually exclusive (400 if both are given); unknown collector names return 400 listing the valid names (the subsystem names of the collectors enabled in this instance). The always-on metrics (opnsense_up, health/status, opnsense_exporter_*) are emitted regardless of filtering.
Prometheus's scrape timeout bounds only the /metrics HTTP request. The request
replays the latest in-memory collector snapshots and never starts OPNsense API
calls. Background polls have their own intervals, request timeout, and concurrency
limit; use the poll freshness, duration, and success metrics to diagnose a slow
firewall endpoint.
Continuous profiling (Pyroscope)¶
The exporter can push continuous profiles to Grafana Cloud Pyroscope using the
pyroscope-go SDK. Profiling is disabled by default and activates only when
--pyroscope.server-address (env OPNSENSE_EXPORTER_PYROSCOPE_SERVER_ADDRESS)
is set. There are no unauthenticated /debug/pprof/* endpoints.
| Flag | Env Var | Default | Description |
|---|---|---|---|
--pyroscope.application-name |
OPNSENSE_EXPORTER_PYROSCOPE_APPLICATION_NAME |
opnsense-exporter |
Pyroscope application name profiles are reported under. |
--pyroscope.auth-password |
OPNSENSE_EXPORTER_PYROSCOPE_AUTH_PASSWORD |
-- | HTTP basic auth password for Pyroscope (Grafana Cloud Access Policy token). This flag/ENV or PYROSCOPE_AUTH_PASSWORD_FILE may be set. |
--pyroscope.auth-user |
OPNSENSE_EXPORTER_PYROSCOPE_AUTH_USER |
-- | HTTP basic auth user for Pyroscope (Grafana Cloud stack/instance ID). This flag/ENV or PYROSCOPE_AUTH_USER_FILE may be set. |
--pyroscope.disable-mutex-block |
OPNSENSE_EXPORTER_PYROSCOPE_DISABLE_MUTEX_BLOCK |
false |
Disable mutex/block contention profiling. On by default; disabling drops the two contention profiles and their process-global sampling rates. CPU, memory, goroutine (and goroutine-leak, when built with the experiment) profiling are unaffected. |
--pyroscope.server-address |
OPNSENSE_EXPORTER_PYROSCOPE_SERVER_ADDRESS |
-- | Grafana Cloud Pyroscope endpoint URL. When empty, continuous profiling is disabled. |
--pyroscope.tenant-id |
OPNSENSE_EXPORTER_PYROSCOPE_TENANT_ID |
-- | Pyroscope tenant ID (only needed for multi-tenancy; unused for Grafana Cloud). |
File-based secrets¶
Like the OPNsense API credentials, the auth user and password can be read from
files instead of flags/env vars: set PYROSCOPE_AUTH_USER_FILE and/or
PYROSCOPE_AUTH_PASSWORD_FILE to a path whose first line holds the value. The
file value takes precedence over the corresponding flag/env var when present
and non-empty.
Profiles are tagged with instance (the resolved instance label) and version.
OTLP metrics export¶
In addition to the /metrics pull endpoint, the exporter can push the exact
same metrics to an OpenTelemetry (OTLP) endpoint. A Prometheus-bridge producer reads
the existing registry on each export tick, so OTLP metric names, labels and values
are identical to what /metrics exposes (no native renaming) - existing dashboards
keep working against either backend. Export is disabled by default and activates
only when --otlp.enabled (env OPNSENSE_EXPORTER_OTLP_ENABLED) is set. The pull
endpoint is unaffected whether or not OTLP is enabled.
--otlp.endpoint, --otlp.headers and --otlp.service-name fall through to the
corresponding standard OpenTelemetry environment variable when left empty
(OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_SERVICE_NAME),
and OTEL_RESOURCE_ATTRIBUTES is read natively by the OTEL SDK. Explicit --otlp.*
flags take precedence over those env vars.
OTEL_EXPORTER_OTLP_PROTOCOL and OTEL_METRIC_EXPORT_INTERVAL are not consulted.
--otlp.protocol and --otlp.export-interval always carry a value (an empty protocol
is rejected at startup rather than defaulted), so the exporter passes both explicitly
and those two env vars never apply - set the flags instead.
| Flag | Env Var | Default | Description |
|---|---|---|---|
--otlp.enabled |
OPNSENSE_EXPORTER_OTLP_ENABLED |
false |
Enable pushing metrics to an OTLP endpoint (in addition to the /metrics pull endpoint). Off by default. |
--otlp.endpoint |
OPNSENSE_EXPORTER_OTLP_ENDPOINT |
-- | OTLP endpoint URL. When empty, the standard OTEL_EXPORTER_OTLP_ENDPOINT env var is used. |
--otlp.export-interval |
OPNSENSE_EXPORTER_OTLP_EXPORT_INTERVAL |
60s |
Interval between OTLP metric exports (independent of Prometheus scrapes). |
--otlp.fast-export-interval |
OPNSENSE_EXPORTER_OTLP_FAST_EXPORT_INTERVAL |
0s |
Optional second OTLP export lane for fast-tier collectors only (#390). Zero (the default) keeps the single-stream behaviour exactly. When set, fast-tier collectors (gateways, interfaces, protocol, pf_stats, activity, netflow, carp — or whatever --collector.poll-interval-override makes fast) export at this interval while everything else stays on --otlp.export-interval. Must be shorter than --otlp.export-interval. Fast-tier series are a small fraction of the total, so 15s here costs far less than setting --otlp.export-interval=15s for everything. |
--otlp.grafana-cloud-endpoint |
OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_ENDPOINT |
-- | Grafana Cloud OTLP gateway base URL (required when using the Grafana Cloud shortcut). |
--otlp.grafana-cloud-instance-id |
OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_INSTANCE_ID |
-- | Grafana Cloud OTLP instance ID. With --otlp.grafana-cloud-token, synthesizes basic-auth. This flag/ENV or OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_INSTANCE_ID_FILE may be set. |
--otlp.grafana-cloud-token |
OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_TOKEN |
-- | Grafana Cloud Access Policy token. This flag/ENV or OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_TOKEN_FILE may be set. |
--otlp.headers |
OPNSENSE_EXPORTER_OTLP_HEADERS |
-- | OTLP headers as comma-separated key=value pairs (e.g. X-Scope-OrgID=1,Authorization=Bearer x). When set, replaces OTEL_EXPORTER_OTLP_HEADERS entirely; when empty, that env var is used. |
--otlp.insecure |
OPNSENSE_EXPORTER_OTLP_INSECURE |
false |
Disable TLS for the OTLP connection (plaintext). |
--otlp.protocol |
OPNSENSE_EXPORTER_OTLP_PROTOCOL |
http/protobuf |
OTLP transport protocol: grpc or http/protobuf. Defaults to http/protobuf; an empty value is rejected. |
--otlp.service-name |
OPNSENSE_EXPORTER_OTLP_SERVICE_NAME |
opnsense-exporter |
service.name resource attribute for exported metrics. |
--otlp.tls-ca-file |
OPNSENSE_EXPORTER_OTLP_TLS_CA_FILE |
-- | Path to a CA certificate file used to verify the OTLP server. |
--otlp.tls-cert-file |
OPNSENSE_EXPORTER_OTLP_TLS_CERT_FILE |
-- | Path to a client certificate file for OTLP mutual TLS (requires --otlp.tls-key-file). |
--otlp.tls-key-file |
OPNSENSE_EXPORTER_OTLP_TLS_KEY_FILE |
-- | Path to a client key file for OTLP mutual TLS (requires --otlp.tls-cert-file). |
The metric set exported over OTLP is the same as the Prometheus
catalogue (see the metrics reference), with one addition
described below: a synthetic up series.
Delivery health¶
--otlp.enabled starting cleanly proves nothing about delivery: the OTLP exporter
connects lazily, so the "otlp metrics export enabled" log line is written before any
network I/O happens. A wrong endpoint, an expired credential or a backend outage can
therefore deliver zero metrics indefinitely.
Four self-metrics make that visible on /metrics and on the operator console:
opnsense_exporter_otlp_exports_total{result="success"|"error"},
opnsense_exporter_otlp_consecutive_failures,
opnsense_exporter_otlp_last_success_timestamp_seconds and
opnsense_exporter_otlp_enabled. Note that otlp_enabled = 1 means the pipeline is
running, not that it is working - the outage signal is a rising
consecutive_failures.
These cannot reach a pure-OTLP backend during an outage, because an exporter cannot
ship its own failure through the path that is failing. On a pure-push deployment they
are for the local console and for post-recovery forensics; the in-band symptom at the
backend is data staleness. Where /metrics is also scraped, they alert normally.
Construction failure is fatal. If --otlp.enabled is set and the exporter cannot
be built, the process exits rather than serving /metrics behind a permanently dead
push pipeline. Export failures after startup are not fatal - they are counted, logged
(rate-limited) and retried, so a flaky backend never takes down the pull endpoint.
Two-speed export (--otlp.fast-export-interval)¶
Collectors already poll on data-volatility tiers, but OTLP exports the whole snapshot
on one interval. Setting --otlp.export-interval=15s to get responsive gateway and
interface graphs therefore re-sends every cold and medium series four times a minute
as well, even though almost none of them changed.
--otlp.fast-export-interval adds an optional second export lane carrying only
the fast-tier collectors, while everything else stays on --otlp.export-interval. It
is off by default (0s), and the default configuration builds exactly one reader,
byte-for-byte as before. It must be shorter than --otlp.export-interval; a fast lane
that is not faster is rejected at startup rather than silently doubling export calls.
Measured on a live deployment (7,226 total series, of which 494 are fast-tier):
| Configuration | Data points per minute | vs 60s baseline |
|---|---|---|
--otlp.export-interval=60s (default) |
7,226 | 1.00x |
--otlp.export-interval=15s (everything fast) |
28,904 | 4.00x |
--otlp.export-interval=60s + --otlp.fast-export-interval=15s |
8,708 | 1.21x |
Fast-tier membership follows each collector's effective poll interval, so a
--collector.poll-interval-override moves a collector between lanes in either
direction. The two lanes are disjoint by construction - the base lane carries every
non-fast collector plus the health, up and exporter self-metrics, the fast lane
carries fast-tier collectors only - so no series is ever exported twice. Per-collector
scheduler metrics travel with their collector, keeping them at the same resolution as
the data they describe.
The trade-off to understand is backend staleness: non-fast series now arrive only
once per --otlp.export-interval, exactly as before, so a dashboard mixing a fast
series with a cold one will show the cold one stepping at the base interval. That is
already true of the underlying poll tiers - a 15m-tier collector cannot be fresher
than 15m no matter how often it is exported - so exporting it more often only inflates
cost, never resolution.
Liveness (up) in push mode¶
When Prometheus scrapes /metrics it synthesizes an up series per target for
free - 1 when the scrape succeeded, 0/absent when the exporter was unreachable -
and liveness alerts (up == 0, absent(up)) key off it. In OTLP push mode there
is no scraper, so nothing generates that series and those alerts silently stop
working.
To keep them working, the exporter emits its own up series, but only over
OTLP: a gauge fixed at 1 while the exporter is running and exporting, labelled
with opnsense_instance. When the exporter stops, it stops pushing and the series
goes stale/absent - exactly the signal an absent(up) (or staleness) alert needs.
This mirrors Prometheus target-up semantics: up reports whether the exporter
is alive, not whether the firewall behind it is healthy (that is
opnsense_up, which reflects OPNsense API reachability).
The synthetic up is deliberately not exposed at /metrics: a literal up
there would collide with the up a Prometheus server generates for the scrape
target. It therefore exists in the pushed OTLP stream alone, and does not appear in
the metrics reference (which catalogues the pull endpoint).
Grafana Cloud shortcut¶
Setting --otlp.grafana-cloud-instance-id, --otlp.grafana-cloud-token and
--otlp.grafana-cloud-endpoint together synthesizes the
Authorization: Basic base64(instanceID:token) header and uses the gateway URL as
the endpoint, so you do not have to assemble the basic-auth header yourself. An
explicit --otlp.endpoint or Authorization header always wins over the shortcut.
The instance ID and token also support *_FILE secret variants
(OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_INSTANCE_ID_FILE,
OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_TOKEN_FILE), whose file contents take
precedence over the flag/env value, mirroring the OPNsense API credentials.
Temporality¶
Exported metrics are always cumulative, and this is not configurable. They are sourced from the Prometheus registry via a bridge producer, so they arrive already aggregated as cumulative (Prometheus' model) and are exported as-is - exactly the temporality Grafana Cloud's metrics backend (Mimir) and Prometheus' OTLP ingest require. An exporter-side temporality selector cannot re-aggregate producer-supplied metrics, so no delta option is offered.
Resource attributes and service_version¶
The exporter puts service.name and service.instance.id on the OTLP resource
for metrics, alongside whatever the SDK's detectors and OTEL_RESOURCE_ATTRIBUTES
contribute. None of them are copied onto individual datapoints: under the
OTLP→Prometheus convention a resource attribute stays on the resource, and the
backend decides what to make of it. Conventionally that means
service.name(+service.namespace) becomes job, service.instance.id becomes
instance, and everything else lands on the target_info series.
service.version is deliberately left off the metrics resource. Backends
deviate from that convention, and Grafana Cloud in particular promotes a fixed list
of resource attributes to a label on every series - service.version among them.
An attribute that is absent cannot be promoted, so omitting it is what keeps
service_version off the metric surface; the alternative would be
asking Grafana Support
to change the list per tenant.
The consequence being avoided: with the label present, every build is a distinct
series, so for a few minutes after each redeploy a rate-based aggregation sees the
old build's series decaying alongside the new one's and over-reports. Aggregating
the label away does not help - that sums both series, which is the same thing. It
bites hardest on per-commit builds, but it also grows active-series cardinality by
the number of versions ever seen. Because the attribute is now absent, target_info
carries no service_version either.
Shipped logs keep service.version on their resource, which is a different
trade: log records are never summed and have no per-series label surface, so the
version is free there and (unless a tenant promotes it) arrives as structured
metadata for per-record version attribution.
Read the version back from the exporter's own info metric, which carries it on every backend, pull or push:
Attribute other series to a build by joining against it:
Collector switches¶
All collectors are enabled by default unless noted otherwise. Each can be individually disabled or enabled using CLI flags or environment variables.
Enabled by default (disable with flag)¶
| Flag | Env Var | Collector | Description |
|---|---|---|---|
--exporter.disable-acme |
OPNSENSE_EXPORTER_DISABLE_ACME |
ACME Client | Disable the scraping of ACME client certificate renewal status and expiry metrics (silent when the os-acme-client plugin is absent) |
--exporter.disable-apcupsd |
OPNSENSE_EXPORTER_DISABLE_APCUPSD |
APC UPS (apcupsd) | Disable the scraping of APC UPS (apcupsd) metrics (silent when the os-apcupsd plugin is absent) |
--exporter.disable-arp-table |
OPNSENSE_EXPORTER_DISABLE_ARP_TABLE |
ARP Table | Disable the scraping of the ARP table |
--exporter.disable-activity |
OPNSENSE_EXPORTER_DISABLE_ACTIVITY |
Activity | Disable the scraping of system activity metrics (CPU percentages, thread counts) |
--exporter.disable-bpf |
OPNSENSE_EXPORTER_DISABLE_BPF |
BPF Statistics | Disable the scraping of BPF listener statistics |
--exporter.disable-carp |
OPNSENSE_EXPORTER_DISABLE_CARP |
CARP | Disable the scraping of CARP/VIP status metrics |
--exporter.disable-captiveportal |
OPNSENSE_EXPORTER_DISABLE_CAPTIVEPORTAL |
Captive Portal | Disable the scraping of captive portal zone/session metrics (silent when no zones are configured) |
--exporter.disable-certificates |
OPNSENSE_EXPORTER_DISABLE_CERTIFICATES |
Certificates | Disable the scraping of certificate expiry metrics |
--exporter.disable-chrony |
OPNSENSE_EXPORTER_DISABLE_CHRONY |
Chrony | Disable the scraping of chrony NTP tracking/source metrics (silent when the os-chrony plugin is absent) |
--exporter.disable-clamav |
OPNSENSE_EXPORTER_DISABLE_CLAMAV |
ClamAV | Disable the scraping of ClamAV engine version and signature database freshness metrics (silent when the os-clamav plugin is absent) |
--exporter.disable-backup |
OPNSENSE_EXPORTER_DISABLE_BACKUP |
Config Backup | Disable the scraping of config backup freshness metrics (last backup timestamp/count/size) |
--exporter.disable-cron-table |
OPNSENSE_EXPORTER_DISABLE_CRON_TABLE |
Cron | Disable the scraping of the cron table |
--exporter.disable-crowdsec |
OPNSENSE_EXPORTER_DISABLE_CROWDSEC |
CrowdSec | Disable the scraping of CrowdSec alert/decision/bouncer/machine counts (silent when the os-crowdsec plugin is absent) |
--exporter.disable-dnsmasq |
OPNSENSE_EXPORTER_DISABLE_DNSMASQ |
Dnsmasq DHCP | Disable the scraping of Dnsmasq DHCP leases |
--exporter.disable-dyndns |
OPNSENSE_EXPORTER_DISABLE_DYNDNS |
DynDNS | Disable the scraping of DynDNS (ddclient) account update status metrics (silent when the os-ddclient plugin is absent) |
--exporter.disable-frr |
OPNSENSE_EXPORTER_DISABLE_FRR |
FRR Routing (BGP/OSPF/BFD) | Disable the scraping of FRR routing metrics (BGP/OSPF/BFD; silent when the os-frr plugin is absent) |
--exporter.disable-feature-availability |
OPNSENSE_EXPORTER_DISABLE_FEATURE_AVAILABILITY |
Feature Availability | Disable the feature-availability collector (opnsense_feature_available; #517). It periodically probes the plugin-gated endpoints backing the opt-in SMART/Tor/Vnstat collectors and logs a one-shot line naming the flag to enable any that answer successfully but are not yet enabled. |
--exporter.disable-firewall |
OPNSENSE_EXPORTER_DISABLE_FIREWALL |
Firewall | Disable the scraping of the firewall (pf) metrics |
--exporter.disable-alias |
OPNSENSE_EXPORTER_DISABLE_ALIAS |
Firewall Aliases | Disable the scraping of firewall alias table sizes |
--exporter.disable-firewall-rules |
OPNSENSE_EXPORTER_DISABLE_FIREWALL_RULES |
Firewall Rules | Disable the scraping of firewall rule statistics |
--exporter.disable-firmware |
OPNSENSE_EXPORTER_DISABLE_FIRMWARE |
Firmware | Disable the scraping of the firmware metrics |
--exporter.disable-flow |
OPNSENSE_EXPORTER_DISABLE_FLOW |
Flow Volume | Disable the flow collector (Prometheus byte/packet volume counters rolled up from flow records, on bounded dimensions). Silent until a flow source - today the Zenarmor receiver - is enabled and feeding it. |
--exporter.disable-gateways |
OPNSENSE_EXPORTER_DISABLE_GATEWAYS |
Gateways | Disable the scraping of gateway status metrics (RTT, packet loss, gateway state) |
--exporter.disable-haproxy |
OPNSENSE_EXPORTER_DISABLE_HAPROXY |
HAProxy | Disable the scraping of HAProxy statistics (silent when the os-haproxy plugin is absent) |
--exporter.disable-hardware |
OPNSENSE_EXPORTER_DISABLE_HARDWARE |
Hardware | Disable the scraping of hardware identity/PSU metrics (DMI system info via os-dmidecode; Deciso DEC-series PSU status via os-dec-hw). Silent when neither plugin is installed. |
--exporter.disable-hostdiscovery |
OPNSENSE_EXPORTER_DISABLE_HOSTDISCOVERY |
Host Discovery | Disable the scraping of the discovered-host inventory (Interfaces > Host discovery / hostwatch): interface+source host counts, low-cardinality. A core OPNsense feature (not a plugin); reads absent/silent on releases predating it. |
--exporter.disable-ids |
OPNSENSE_EXPORTER_DISABLE_IDS |
IDS/IPS (Suricata) | Disable the scraping of Suricata IDS/IPS metrics (service status, IPS mode, eve log and ruleset inventory, installed-rule count; silent structures when IDS is unconfigured) |
--exporter.disable-ipsec |
OPNSENSE_EXPORTER_DISABLE_IPSEC |
IPsec | Disable the scraping of IPSec service |
--exporter.disable-dhcpv4 |
OPNSENSE_EXPORTER_DISABLE_DHCPV4 |
ISC DHCPv4 | Disable the scraping of ISC DHCPv4 leases (silent when the legacy ISC DHCP backend is absent) |
--exporter.disable-dhcpv6 |
OPNSENSE_EXPORTER_DISABLE_DHCPV6 |
ISC DHCPv6 | Disable the scraping of ISC DHCPv6 leases and delegated prefixes (silent when the legacy ISC DHCP backend is absent) |
--exporter.disable-interfaces |
OPNSENSE_EXPORTER_DISABLE_INTERFACES |
Interfaces | Disable the interfaces collector (per-interface traffic/link metrics) |
--exporter.disable-kea |
OPNSENSE_EXPORTER_DISABLE_KEA |
Kea DHCP | Disable the scraping of Kea DHCP lease metrics |
--exporter.disable-lldpd |
OPNSENSE_EXPORTER_DISABLE_LLDPD |
LLDP Neighbors | Disable the scraping of LLDP neighbor table metrics (silent when the os-lldpd plugin is absent) |
--exporter.disable-auth |
OPNSENSE_EXPORTER_DISABLE_AUTH |
Local Auth | Disable the scraping of local-auth security-posture metrics (user/group/API-key counts, aggregates only - no per-user data) |
--exporter.disable-log-events |
OPNSENSE_EXPORTER_DISABLE_LOG_EVENTS |
Log-derived Events | Disable the log_events collector (Prometheus counters derived from received syslog lines: firewall/haproxy/sshd/dhcp/audit/ids event totals). Silent until the syslog receiver is enabled and feeding it. |
--exporter.disable-mbuf |
OPNSENSE_EXPORTER_DISABLE_MBUF |
Mbuf | Disable the scraping of mbuf statistics |
--exporter.disable-monit |
OPNSENSE_EXPORTER_DISABLE_MONIT |
Monit | Disable the scraping of Monit service check status (silent when Monit is not running) |
--exporter.disable-ndp |
OPNSENSE_EXPORTER_DISABLE_NDP |
NDP | Disable the scraping of the NDP (IPv6 neighbor discovery) table |
--exporter.disable-ntp |
OPNSENSE_EXPORTER_DISABLE_NTP |
NTP | Disable the scraping of NTP peer metrics |
--exporter.disable-nut |
OPNSENSE_EXPORTER_DISABLE_NUT |
NUT UPS | Disable the scraping of NUT UPS metrics (silent when the os-nut plugin is absent) |
--exporter.disable-netbird |
OPNSENSE_EXPORTER_DISABLE_NETBIRD |
NetBird | Disable the scraping of NetBird management/signal connectivity, relay and peer metrics (silent when the os-netbird plugin is absent) |
--exporter.disable-nginx |
OPNSENSE_EXPORTER_DISABLE_NGINX |
Nginx | Disable the scraping of nginx VTS statistics (silent when the os-nginx plugin is absent) |
--exporter.disable-openvpn |
OPNSENSE_EXPORTER_DISABLE_OPENVPN |
OpenVPN | Disable the scraping of OpenVPN service |
--exporter.disable-pf-stats |
OPNSENSE_EXPORTER_DISABLE_PF_STATS |
PF Statistics | Disable the scraping of PF statistics (state table, counters, memory limits, timeouts) |
--exporter.disable-protocol |
OPNSENSE_EXPORTER_DISABLE_PROTOCOL |
Protocol Statistics | Disable the protocol-statistics collector (TCP/UDP/IP/ICMP/ARP/CARP/pfsync counters) |
--exporter.disable-qfeeds |
OPNSENSE_EXPORTER_DISABLE_QFEEDS |
Q-Feeds | Disable the scraping of Q-Feeds threat intelligence statistics (silent when the os-q-feeds-connector plugin is absent) |
--exporter.disable-relayd |
OPNSENSE_EXPORTER_DISABLE_RELAYD |
Relayd Load Balancer | Disable the scraping of relayd virtual server/table/host health (silent when the os-relayd plugin is absent) |
--exporter.disable-services |
OPNSENSE_EXPORTER_DISABLE_SERVICES |
Services | Disable the services collector (per-service running state) |
--exporter.disable-siproxd |
OPNSENSE_EXPORTER_DISABLE_SIPROXD |
Siproxd | Disable the scraping of the siproxd active SIP registration count (silent when the os-siproxd plugin is absent) |
--exporter.disable-syslog |
OPNSENSE_EXPORTER_DISABLE_SYSLOG |
Syslog | Disable the scraping of syslog-ng statistics |
--exporter.disable-system |
OPNSENSE_EXPORTER_DISABLE_SYSTEM |
System | Disable the scraping of system resource metrics (memory, uptime, disk, swap) |
--exporter.disable-tailscale |
OPNSENSE_EXPORTER_DISABLE_TAILSCALE |
Tailscale | Disable the scraping of Tailscale node-local metrics (silent when the os-tailscale plugin is absent; complementary to tailscale2otel) |
--exporter.disable-temperature |
OPNSENSE_EXPORTER_DISABLE_TEMPERATURE |
Temperature | Disable the scraping of temperature metrics |
--exporter.disable-trafficshaper |
OPNSENSE_EXPORTER_DISABLE_TRAFFICSHAPER |
Traffic Shaper | Disable the scraping of traffic shaper pipe/queue/rule statistics (silent when the shaper is unconfigured) |
--exporter.disable-unbound |
OPNSENSE_EXPORTER_DISABLE_UNBOUND |
Unbound DNS | Disable the scraping of Unbound service |
--exporter.disable-wireguard |
OPNSENSE_EXPORTER_DISABLE_WIREGUARD |
Wireguard | Disable the scraping of Wireguard service |
--exporter.disable-snapshots |
OPNSENSE_EXPORTER_DISABLE_SNAPSHOTS |
ZFS Boot Environments | Disable the scraping of ZFS boot-environment inventory metrics (silent/zero on non-ZFS filesystems such as UFS) |
Always-on collectors
The Interfaces, Protocol Statistics, Services, and built-in health-check collectors are always enabled and have no disable flag.
Disabled by default (opt-in with flag)¶
These collectors are disabled by default because each scheduled poll adds API calls or expensive work on OPNsense. Enable them only if you need the data.
| Flag | Env Var | Collector | Description |
|---|---|---|---|
--exporter.enable-hasync |
OPNSENSE_EXPORTER_ENABLE_HASYNC |
HA Sync Status | Enable the HA sync status collector (performs a live XML-RPC call to the CARP peer on every scheduled poll). Disabled by default. |
--exporter.enable-netflow |
OPNSENSE_EXPORTER_ENABLE_NETFLOW |
NetFlow | Enable the netflow collector (enabled status, service status, cache stats). Disabled by default. |
--exporter.enable-network-diagnostics |
OPNSENSE_EXPORTER_ENABLE_NETWORK_DIAGNOSTICS |
Network Diagnostics | Enable the network diagnostics collector (netisr, sockets, routes). Disabled by default. |
--exporter.enable-smart |
OPNSENSE_EXPORTER_ENABLE_SMART |
SMART Disk Health | Enable the SMART disk health collector. Off by default: each scheduled poll does a per-disk POST fanout that runs smartctl -a on the firewall (extra API/latency cost, and wakes spun-down disks). Silent when the os-smart plugin is absent. |
--exporter.enable-tor |
OPNSENSE_EXPORTER_ENABLE_TOR |
Tor | Enable the Tor circuit/stream telemetry collector (control-port GETINFO via the os-tor plugin). Off by default: each scheduled poll does two extra configd execs to query the control port, and requires the plugin's control port + password to be configured. Silent when the os-tor plugin is absent. |
--exporter.enable-vnstat |
OPNSENSE_EXPORTER_ENABLE_VNSTAT |
Vnstat Traffic Accounting | Enable the vnstat persistent traffic accounting collector (day/month/total bytes per interface, survives reboots). Off by default: each scheduled poll does one interface_list call plus one get_json_data call per interface vnstat tracks. Silent when the os-vnstat plugin is absent. |
High-cardinality detail options¶
These flags enable per-item detail metrics that can produce a large number of time series. Each unique label combination creates a separate time series in Prometheus.
Evaluate before enabling
On a firewall with hundreds of DHCP leases or firewall rules, enabling detail metrics can produce thousands of time series. Monitor your Prometheus storage and ingestion rate after enabling.
| Flag | Env Var | Collector | Description |
|---|---|---|---|
--exporter.enable-arp-details |
OPNSENSE_EXPORTER_ENABLE_ARP_DETAILS |
ARP Table | Enable per-entry ARP metrics (ip/mac/hostname labels - high, churning cardinality). Off by default; the low-cardinality entries_total aggregate is always emitted. |
--exporter.enable-dnsmasq-details |
OPNSENSE_EXPORTER_ENABLE_DNSMASQ_DETAILS |
Dnsmasq DHCP | Enable per-lease detail metrics for Dnsmasq DHCP (high cardinality on large networks) |
--exporter.enable-frr-routes |
OPNSENSE_EXPORTER_ENABLE_FRR_ROUTES |
FRR Routing (BGP/OSPF/BFD) | Enable FRR routing-state volume gauges (zebra RIB / OSPF route table / LSDB counts by protocol, route type, area and LSA type - never per-prefix or per-LSA series). Off by default: the underlying bootgrid endpoints have no success-body caching and their payload size scales with route-table size (up to 6 extra vtysh execs per scheduled poll). |
--exporter.enable-firewall-nat-counts |
OPNSENSE_EXPORTER_ENABLE_FIREWALL_NAT_COUNTS |
Firewall | Enable the NAT rule inventory count metric (opnsense_firewall_nat_rules), broken down by type (source_nat, d_nat, one_to_one, npt) and enabled state. Off by default: each scheduled poll does four extra GETs, one per NAT rule type. Rules created before an admin migrated to the MVC-managed NAT backend are not counted; NAT rule pf hit/byte statistics do not exist upstream. |
--exporter.enable-alias-details |
OPNSENSE_EXPORTER_ENABLE_ALIAS_DETAILS |
Firewall Aliases | Enable per-table pf evaluation/packet/byte counters for firewall aliases (~10 series per alias table) |
--exporter.enable-firewall-rules-details |
OPNSENSE_EXPORTER_ENABLE_FIREWALL_RULES_DETAILS |
Firewall Rules | Enable per-rule detail metrics for firewall rules (high cardinality on large rulesets) |
--exporter.enable-firmware-package-details |
OPNSENSE_EXPORTER_ENABLE_FIRMWARE_PACKAGE_DETAILS |
Firmware | Enable per-package firmware detail metrics (pending package updates and installed plugin inventory; adds one extra API call per scheduled poll) |
--exporter.enable-ids-alerts |
OPNSENSE_EXPORTER_ENABLE_IDS_ALERTS |
IDS/IPS (Suricata) | Enable the Suricata recent-alerts gauge (opnsense_ids_recent_alerts by action). Off by default: each scheduled poll triggers a reverse read of eve.json on the box. Window set by --exporter.ids-alert-lookback. |
--exporter.enable-ipsec-lease-details |
OPNSENSE_EXPORTER_ENABLE_IPSEC_LEASE_DETAILS |
IPsec | Enable per-lease IPsec mode-cfg detail metrics (opnsense_ipsec_lease_online with an unbounded road-warrior user label). Off by default; the per-pool lease aggregates stay always-on. |
--exporter.enable-dhcpv4-details |
OPNSENSE_EXPORTER_ENABLE_DHCPV4_DETAILS |
ISC DHCPv4 | Enable per-lease detail metrics for ISC DHCPv4 (high cardinality on large networks) |
--exporter.enable-dhcpv6-details |
OPNSENSE_EXPORTER_ENABLE_DHCPV6_DETAILS |
ISC DHCPv6 | Enable per-lease detail metrics for ISC DHCPv6 (high cardinality on large networks) |
--exporter.enable-kea-details |
OPNSENSE_EXPORTER_ENABLE_KEA_DETAILS |
Kea DHCP | Enable per-lease detail metrics for Kea DHCP (high cardinality on large networks) |
--exporter.enable-ndp-details |
OPNSENSE_EXPORTER_ENABLE_NDP_DETAILS |
NDP | Enable per-entry NDP metrics (ip/mac labels - high, churning cardinality from IPv6 privacy-address rotation). Off by default; the low-cardinality entries_total aggregate is always emitted. |
--exporter.enable-netbird-details |
OPNSENSE_EXPORTER_ENABLE_NETBIRD_DETAILS |
NetBird | Enable per-peer detail metrics for NetBird (per-peer cardinality; peer FQDN labels) |
--exporter.enable-openvpn-details |
OPNSENSE_EXPORTER_ENABLE_OPENVPN_DETAILS |
OpenVPN | Enable per-session detail metrics for OpenVPN (exposes usernames and per-client tunnel addresses) |
--exporter.enable-tailscale-peer-details |
OPNSENSE_EXPORTER_ENABLE_TAILSCALE_PEER_DETAILS |
Tailscale | Enable per-peer detail metrics for Tailscale (per-peer cardinality; peer hostname labels) |
--exporter.enable-unbound-qstats |
OPNSENSE_EXPORTER_ENABLE_UNBOUND_QSTATS |
Unbound DNS | Enable Unbound DNSBL query-stats totals and blocklist size metrics, plus local-zone/data/insecure-domain counts. Off by default: the query-stats totals call is backed by an expensive configd+python+pandas+DuckDB query (~1s per scheduled poll) - skipped entirely while query-stats logging (general.stats) is off on the box, but still paid for on every scheduled poll once it is on. |
--exporter.enable-unbound-infra |
OPNSENSE_EXPORTER_ENABLE_UNBOUND_INFRA |
Unbound DNS | Enable per-upstream infra cache RTT metrics from Unbound (cardinality scales with the resolver's infra cache; one series pair per upstream ip/host) |
Full flag reference¶
Every flag the exporter accepts, generated from the binary's own flag definitions
(--help shows the same set):
| Flag | Env Var | Default | Description |
|---|---|---|---|
--annotations.enabled |
OPNSENSE_EXPORTER_ANNOTATIONS_ENABLED |
false |
Write OPNsense change events (reboots, configuration changes, interface counter resets, upgrades, certificate renewals, feed updates) into Grafana's annotation store so they overlay any dashboard. Off by default: this is the exporter's only outbound write. |
--annotations.extra-tags |
OPNSENSE_EXPORTER_ANNOTATIONS_EXTRA_TAGS |
-- | Extra tag to add to every written annotation (repeatable), e.g. env:prod. Every annotation already carries opnsense-exporter, the event kind and instance: |
--annotations.grafana-url |
OPNSENSE_EXPORTER_ANNOTATIONS_GRAFANA_URL |
-- | Grafana base URL to write annotations to, e.g. https://mystack.grafana.net. |
--annotations.interval |
OPNSENSE_EXPORTER_ANNOTATIONS_INTERVAL |
60s |
How often the watched event metrics are checked for changes. This bounds how late an annotation is WRITTEN, never where it is PLACED — each annotation carries the event's own timestamp. |
--annotations.lookback |
OPNSENSE_EXPORTER_ANNOTATIONS_LOOKBACK |
24h |
How old an event may be and still be worth annotating, and how far back the startup reconciliation looks for annotations this exporter already wrote. Keeps a fresh deployment from annotating a reboot that happened months ago. Read this together with --annotations.max-per-cycle: a fresh deployment finds every event inside this window at once, and that first-run backlog drains at most max-per-cycle annotations per --annotations.interval (default 20/60s), so a 24h lookback on a busy firewall takes several minutes to catch up. Shorten this if you want a fresh deployment to start clean rather than backfill a day. |
--annotations.max-per-cycle |
OPNSENSE_EXPORTER_ANNOTATIONS_MAX_PER_CYCLE |
20 |
Maximum annotation posts ATTEMPTED per check, successful or not. A guard against one bad reading writing hundreds of annotations, not a rate limit to tune. It also paces the first-run backlog --annotations.lookback produces: the excess is not marked seen, so it is re-proposed on the next check and a deployment with a 24h lookback drains at this many per --annotations.interval until it is caught up. Events are only lost if they age out of the lookback before the backlog reaches them. Raising it drains faster but makes a rate limit (opnsense_exporter_annotations_rate_limited_total) more likely, since a Grafana org shares one annotation limit across every writer. |
--annotations.timeout |
OPNSENSE_EXPORTER_ANNOTATIONS_TIMEOUT |
10s |
Timeout for each Grafana annotation API request. |
--annotations.token |
OPNSENSE_EXPORTER_ANNOTATIONS_TOKEN |
-- | Grafana service-account token used to write annotations. It needs the annotation write permission and nothing else. This flag/ENV or OPNSENSE_EXPORTER_ANNOTATIONS_TOKEN_FILE may be set. |
--collector.health-poll-interval |
OPNSENSE_EXPORTER_COLLECTOR_HEALTH_POLL_INTERVAL |
60s |
Interval at which the exporter polls the OPNsense health endpoint (#386). This is the circuit-breaker cadence: the health poll sets and clears the process-wide 'firewall unreachable' flag, so it bounds how quickly collectors resume after the box recovers. Independent of --collector.poll-interval since #386, which previously controlled it by accident. Clamped to [5s, 15m]. |
--collector.poll-interval |
OPNSENSE_EXPORTER_COLLECTOR_POLL_INTERVAL |
60s |
Default interval at which each collector polls the OPNsense API into the in-memory snapshot that /metrics and the OTLP bridge replay (#336). A collector may declare its own faster/slower tier; every interval is clamped to [5s, 15m]. |
--collector.poll-interval-override |
OPNSENSE_EXPORTER_COLLECTOR_POLL_INTERVAL_OVERRIDE |
-- | Override a specific collector's poll interval as |
--config.check |
-- | -- | Validate the effective configuration and exit, without binding any port, starting the poll scheduler, contacting OPNsense, or exporting telemetry. Exits 0 when the configuration is usable and 1 otherwise. Referenced files (API key/secret, TLS keypairs) are read; network reachability is deliberately not checked (that is what /-/ready is for). Has no env var by design: an ambient one would turn every start into a no-op. |
--exporter.cache-ttl |
OPNSENSE_EXPORTER_CACHE_TTL |
1h0m0s |
How long to cache responses from slow-moving API endpoints (system/CPU identity, certificate inventory, Unbound DNS blocklist policy config) and to remember that a plugin-gated endpoint is absent (its 404). This data changes only on an admin action - a config edit, a certificate renewal, a plugin install - so re-fetching it on every poll only costs firewall CPU. Set it above the collector poll interval or it can never serve a hit. The cost is staleness: a newly installed plugin, or a cert change, can take up to this long to show up. Set to 0 to fetch everything on every poll. Live data (counters, rates, service run-state) is never cached regardless of this setting. |
--exporter.disable-acme |
OPNSENSE_EXPORTER_DISABLE_ACME |
false |
Disable the scraping of ACME client certificate renewal status and expiry metrics (silent when the os-acme-client plugin is absent) |
--exporter.disable-activity |
OPNSENSE_EXPORTER_DISABLE_ACTIVITY |
false |
Disable the scraping of system activity metrics (CPU percentages, thread counts) |
--exporter.disable-alias |
OPNSENSE_EXPORTER_DISABLE_ALIAS |
false |
Disable the scraping of firewall alias table sizes |
--exporter.disable-apcupsd |
OPNSENSE_EXPORTER_DISABLE_APCUPSD |
false |
Disable the scraping of APC UPS (apcupsd) metrics (silent when the os-apcupsd plugin is absent) |
--exporter.disable-arp-table |
OPNSENSE_EXPORTER_DISABLE_ARP_TABLE |
false |
Disable the scraping of the ARP table |
--exporter.disable-auth |
OPNSENSE_EXPORTER_DISABLE_AUTH |
false |
Disable the scraping of local-auth security-posture metrics (user/group/API-key counts, aggregates only - no per-user data) |
--exporter.disable-backup |
OPNSENSE_EXPORTER_DISABLE_BACKUP |
false |
Disable the scraping of config backup freshness metrics (last backup timestamp/count/size) |
--exporter.disable-bpf |
OPNSENSE_EXPORTER_DISABLE_BPF |
false |
Disable the scraping of BPF listener statistics |
--exporter.disable-captiveportal |
OPNSENSE_EXPORTER_DISABLE_CAPTIVEPORTAL |
false |
Disable the scraping of captive portal zone/session metrics (silent when no zones are configured) |
--exporter.disable-carp |
OPNSENSE_EXPORTER_DISABLE_CARP |
false |
Disable the scraping of CARP/VIP status metrics |
--exporter.disable-certificates |
OPNSENSE_EXPORTER_DISABLE_CERTIFICATES |
false |
Disable the scraping of certificate expiry metrics |
--exporter.disable-chrony |
OPNSENSE_EXPORTER_DISABLE_CHRONY |
false |
Disable the scraping of chrony NTP tracking/source metrics (silent when the os-chrony plugin is absent) |
--exporter.disable-clamav |
OPNSENSE_EXPORTER_DISABLE_CLAMAV |
false |
Disable the scraping of ClamAV engine version and signature database freshness metrics (silent when the os-clamav plugin is absent) |
--exporter.disable-cron-table |
OPNSENSE_EXPORTER_DISABLE_CRON_TABLE |
false |
Disable the scraping of the cron table |
--exporter.disable-crowdsec |
OPNSENSE_EXPORTER_DISABLE_CROWDSEC |
false |
Disable the scraping of CrowdSec alert/decision/bouncer/machine counts (silent when the os-crowdsec plugin is absent) |
--exporter.disable-dhcpv4 |
OPNSENSE_EXPORTER_DISABLE_DHCPV4 |
false |
Disable the scraping of ISC DHCPv4 leases (silent when the legacy ISC DHCP backend is absent) |
--exporter.disable-dhcpv6 |
OPNSENSE_EXPORTER_DISABLE_DHCPV6 |
false |
Disable the scraping of ISC DHCPv6 leases and delegated prefixes (silent when the legacy ISC DHCP backend is absent) |
--exporter.disable-dnsmasq |
OPNSENSE_EXPORTER_DISABLE_DNSMASQ |
false |
Disable the scraping of Dnsmasq DHCP leases |
--exporter.disable-dyndns |
OPNSENSE_EXPORTER_DISABLE_DYNDNS |
false |
Disable the scraping of DynDNS (ddclient) account update status metrics (silent when the os-ddclient plugin is absent) |
--exporter.disable-feature-availability |
OPNSENSE_EXPORTER_DISABLE_FEATURE_AVAILABILITY |
false |
Disable the feature-availability collector (opnsense_feature_available; #517). It periodically probes the plugin-gated endpoints backing the opt-in SMART/Tor/Vnstat collectors and logs a one-shot line naming the flag to enable any that answer successfully but are not yet enabled. |
--exporter.disable-firewall |
OPNSENSE_EXPORTER_DISABLE_FIREWALL |
false |
Disable the scraping of the firewall (pf) metrics |
--exporter.disable-firewall-rules |
OPNSENSE_EXPORTER_DISABLE_FIREWALL_RULES |
false |
Disable the scraping of firewall rule statistics |
--exporter.disable-firmware |
OPNSENSE_EXPORTER_DISABLE_FIRMWARE |
false |
Disable the scraping of the firmware metrics |
--exporter.disable-flow |
OPNSENSE_EXPORTER_DISABLE_FLOW |
false |
Disable the flow collector (Prometheus byte/packet volume counters rolled up from flow records, on bounded dimensions). Silent until a flow source - today the Zenarmor receiver - is enabled and feeding it. |
--exporter.disable-frr |
OPNSENSE_EXPORTER_DISABLE_FRR |
false |
Disable the scraping of FRR routing metrics (BGP/OSPF/BFD; silent when the os-frr plugin is absent) |
--exporter.disable-gateways |
OPNSENSE_EXPORTER_DISABLE_GATEWAYS |
false |
Disable the scraping of gateway status metrics (RTT, packet loss, gateway state) |
--exporter.disable-haproxy |
OPNSENSE_EXPORTER_DISABLE_HAPROXY |
false |
Disable the scraping of HAProxy statistics (silent when the os-haproxy plugin is absent) |
--exporter.disable-hardware |
OPNSENSE_EXPORTER_DISABLE_HARDWARE |
false |
Disable the scraping of hardware identity/PSU metrics (DMI system info via os-dmidecode; Deciso DEC-series PSU status via os-dec-hw). Silent when neither plugin is installed. |
--exporter.disable-hostdiscovery |
OPNSENSE_EXPORTER_DISABLE_HOSTDISCOVERY |
false |
Disable the scraping of the discovered-host inventory (Interfaces > Host discovery / hostwatch): interface+source host counts, low-cardinality. A core OPNsense feature (not a plugin); reads absent/silent on releases predating it. |
--exporter.disable-ids |
OPNSENSE_EXPORTER_DISABLE_IDS |
false |
Disable the scraping of Suricata IDS/IPS metrics (service status, IPS mode, eve log and ruleset inventory, installed-rule count; silent structures when IDS is unconfigured) |
--exporter.disable-interfaces |
OPNSENSE_EXPORTER_DISABLE_INTERFACES |
false |
Disable the interfaces collector (per-interface traffic/link metrics) |
--exporter.disable-ipsec |
OPNSENSE_EXPORTER_DISABLE_IPSEC |
false |
Disable the scraping of IPSec service |
--exporter.disable-kea |
OPNSENSE_EXPORTER_DISABLE_KEA |
false |
Disable the scraping of Kea DHCP lease metrics |
--exporter.disable-lldpd |
OPNSENSE_EXPORTER_DISABLE_LLDPD |
false |
Disable the scraping of LLDP neighbor table metrics (silent when the os-lldpd plugin is absent) |
--exporter.disable-log-events |
OPNSENSE_EXPORTER_DISABLE_LOG_EVENTS |
false |
Disable the log_events collector (Prometheus counters derived from received syslog lines: firewall/haproxy/sshd/dhcp/audit/ids event totals). Silent until the syslog receiver is enabled and feeding it. |
--exporter.disable-mbuf |
OPNSENSE_EXPORTER_DISABLE_MBUF |
false |
Disable the scraping of mbuf statistics |
--exporter.disable-monit |
OPNSENSE_EXPORTER_DISABLE_MONIT |
false |
Disable the scraping of Monit service check status (silent when Monit is not running) |
--exporter.disable-ndp |
OPNSENSE_EXPORTER_DISABLE_NDP |
false |
Disable the scraping of the NDP (IPv6 neighbor discovery) table |
--exporter.disable-netbird |
OPNSENSE_EXPORTER_DISABLE_NETBIRD |
false |
Disable the scraping of NetBird management/signal connectivity, relay and peer metrics (silent when the os-netbird plugin is absent) |
--exporter.disable-nginx |
OPNSENSE_EXPORTER_DISABLE_NGINX |
false |
Disable the scraping of nginx VTS statistics (silent when the os-nginx plugin is absent) |
--exporter.disable-ntp |
OPNSENSE_EXPORTER_DISABLE_NTP |
false |
Disable the scraping of NTP peer metrics |
--exporter.disable-nut |
OPNSENSE_EXPORTER_DISABLE_NUT |
false |
Disable the scraping of NUT UPS metrics (silent when the os-nut plugin is absent) |
--exporter.disable-openvpn |
OPNSENSE_EXPORTER_DISABLE_OPENVPN |
false |
Disable the scraping of OpenVPN service |
--exporter.disable-pf-stats |
OPNSENSE_EXPORTER_DISABLE_PF_STATS |
false |
Disable the scraping of PF statistics (state table, counters, memory limits, timeouts) |
--exporter.disable-protocol |
OPNSENSE_EXPORTER_DISABLE_PROTOCOL |
false |
Disable the protocol-statistics collector (TCP/UDP/IP/ICMP/ARP/CARP/pfsync counters) |
--exporter.disable-qfeeds |
OPNSENSE_EXPORTER_DISABLE_QFEEDS |
false |
Disable the scraping of Q-Feeds threat intelligence statistics (silent when the os-q-feeds-connector plugin is absent) |
--exporter.disable-relayd |
OPNSENSE_EXPORTER_DISABLE_RELAYD |
false |
Disable the scraping of relayd virtual server/table/host health (silent when the os-relayd plugin is absent) |
--exporter.disable-services |
OPNSENSE_EXPORTER_DISABLE_SERVICES |
false |
Disable the services collector (per-service running state) |
--exporter.disable-siproxd |
OPNSENSE_EXPORTER_DISABLE_SIPROXD |
false |
Disable the scraping of the siproxd active SIP registration count (silent when the os-siproxd plugin is absent) |
--exporter.disable-snapshots |
OPNSENSE_EXPORTER_DISABLE_SNAPSHOTS |
false |
Disable the scraping of ZFS boot-environment inventory metrics (silent/zero on non-ZFS filesystems such as UFS) |
--exporter.disable-syslog |
OPNSENSE_EXPORTER_DISABLE_SYSLOG |
false |
Disable the scraping of syslog-ng statistics |
--exporter.disable-system |
OPNSENSE_EXPORTER_DISABLE_SYSTEM |
false |
Disable the scraping of system resource metrics (memory, uptime, disk, swap) |
--exporter.disable-tailscale |
OPNSENSE_EXPORTER_DISABLE_TAILSCALE |
false |
Disable the scraping of Tailscale node-local metrics (silent when the os-tailscale plugin is absent; complementary to tailscale2otel) |
--exporter.disable-temperature |
OPNSENSE_EXPORTER_DISABLE_TEMPERATURE |
false |
Disable the scraping of temperature metrics |
--exporter.disable-trafficshaper |
OPNSENSE_EXPORTER_DISABLE_TRAFFICSHAPER |
false |
Disable the scraping of traffic shaper pipe/queue/rule statistics (silent when the shaper is unconfigured) |
--exporter.disable-unbound |
OPNSENSE_EXPORTER_DISABLE_UNBOUND |
false |
Disable the scraping of Unbound service |
--exporter.disable-wireguard |
OPNSENSE_EXPORTER_DISABLE_WIREGUARD |
false |
Disable the scraping of Wireguard service |
--exporter.enable-alias-details |
OPNSENSE_EXPORTER_ENABLE_ALIAS_DETAILS |
false |
Enable per-table pf evaluation/packet/byte counters for firewall aliases (~10 series per alias table) |
--exporter.enable-all-available |
OPNSENSE_EXPORTER_ENABLE_ALL_AVAILABLE |
false |
Enable every opt-in collector switch (--exporter.enable-*) that is not explicitly set on the command line or via its own env var. A collector whose PLUGIN the startup availability probe finds absent is left off, so this enables what the box can actually serve; anything gated on cost or cardinality rather than a plugin is enabled regardless. If the firewall cannot be reached at startup the probe falls open and everything is enabled. NOTE: because availability is resolved once at startup, a plugin installed LATER does not self-activate under this flag until the next restart. Never enables the syslog/Zenarmor/NetFlow receivers - those open network sockets and are out of scope. Each collector this switches on is logged individually with the reason it defaults to off; an explicit --exporter.enable- |
--exporter.enable-arp-details |
OPNSENSE_EXPORTER_ENABLE_ARP_DETAILS |
false |
Enable per-entry ARP metrics (ip/mac/hostname labels - high, churning cardinality). Off by default; the low-cardinality entries_total aggregate is always emitted. |
--exporter.enable-dhcpv4-details |
OPNSENSE_EXPORTER_ENABLE_DHCPV4_DETAILS |
false |
Enable per-lease detail metrics for ISC DHCPv4 (high cardinality on large networks) |
--exporter.enable-dhcpv6-details |
OPNSENSE_EXPORTER_ENABLE_DHCPV6_DETAILS |
false |
Enable per-lease detail metrics for ISC DHCPv6 (high cardinality on large networks) |
--exporter.enable-dnsmasq-details |
OPNSENSE_EXPORTER_ENABLE_DNSMASQ_DETAILS |
false |
Enable per-lease detail metrics for Dnsmasq DHCP (high cardinality on large networks) |
--exporter.enable-firewall-nat-counts |
OPNSENSE_EXPORTER_ENABLE_FIREWALL_NAT_COUNTS |
false |
Enable the NAT rule inventory count metric (opnsense_firewall_nat_rules), broken down by type (source_nat, d_nat, one_to_one, npt) and enabled state. Off by default: each scheduled poll does four extra GETs, one per NAT rule type. Rules created before an admin migrated to the MVC-managed NAT backend are not counted; NAT rule pf hit/byte statistics do not exist upstream. |
--exporter.enable-firewall-rules-details |
OPNSENSE_EXPORTER_ENABLE_FIREWALL_RULES_DETAILS |
false |
Enable per-rule detail metrics for firewall rules (high cardinality on large rulesets) |
--exporter.enable-firmware-package-details |
OPNSENSE_EXPORTER_ENABLE_FIRMWARE_PACKAGE_DETAILS |
false |
Enable per-package firmware detail metrics (pending package updates and installed plugin inventory; adds one extra API call per scheduled poll) |
--exporter.enable-frr-routes |
OPNSENSE_EXPORTER_ENABLE_FRR_ROUTES |
false |
Enable FRR routing-state volume gauges (zebra RIB / OSPF route table / LSDB counts by protocol, route type, area and LSA type - never per-prefix or per-LSA series). Off by default: the underlying bootgrid endpoints have no success-body caching and their payload size scales with route-table size (up to 6 extra vtysh execs per scheduled poll). |
--exporter.enable-hasync |
OPNSENSE_EXPORTER_ENABLE_HASYNC |
false |
Enable the HA sync status collector (performs a live XML-RPC call to the CARP peer on every scheduled poll). Disabled by default. |
--exporter.enable-ids-alerts |
OPNSENSE_EXPORTER_ENABLE_IDS_ALERTS |
false |
Enable the Suricata recent-alerts gauge (opnsense_ids_recent_alerts by action). Off by default: each scheduled poll triggers a reverse read of eve.json on the box. Window set by --exporter.ids-alert-lookback. |
--exporter.enable-ipsec-lease-details |
OPNSENSE_EXPORTER_ENABLE_IPSEC_LEASE_DETAILS |
false |
Enable per-lease IPsec mode-cfg detail metrics (opnsense_ipsec_lease_online with an unbounded road-warrior user label). Off by default; the per-pool lease aggregates stay always-on. |
--exporter.enable-kea-details |
OPNSENSE_EXPORTER_ENABLE_KEA_DETAILS |
false |
Enable per-lease detail metrics for Kea DHCP (high cardinality on large networks) |
--exporter.enable-ndp-details |
OPNSENSE_EXPORTER_ENABLE_NDP_DETAILS |
false |
Enable per-entry NDP metrics (ip/mac labels - high, churning cardinality from IPv6 privacy-address rotation). Off by default; the low-cardinality entries_total aggregate is always emitted. |
--exporter.enable-netbird-details |
OPNSENSE_EXPORTER_ENABLE_NETBIRD_DETAILS |
false |
Enable per-peer detail metrics for NetBird (per-peer cardinality; peer FQDN labels) |
--exporter.enable-netflow |
OPNSENSE_EXPORTER_ENABLE_NETFLOW |
false |
Enable the netflow collector (enabled status, service status, cache stats). Disabled by default. |
--exporter.enable-network-diagnostics |
OPNSENSE_EXPORTER_ENABLE_NETWORK_DIAGNOSTICS |
false |
Enable the network diagnostics collector (netisr, sockets, routes). Disabled by default. |
--exporter.enable-openvpn-details |
OPNSENSE_EXPORTER_ENABLE_OPENVPN_DETAILS |
false |
Enable per-session detail metrics for OpenVPN (exposes usernames and per-client tunnel addresses) |
--exporter.enable-smart |
OPNSENSE_EXPORTER_ENABLE_SMART |
false |
Enable the SMART disk health collector. Off by default: each scheduled poll does a per-disk POST fanout that runs smartctl -a on the firewall (extra API/latency cost, and wakes spun-down disks). Silent when the os-smart plugin is absent. |
--exporter.enable-tailscale-peer-details |
OPNSENSE_EXPORTER_ENABLE_TAILSCALE_PEER_DETAILS |
false |
Enable per-peer detail metrics for Tailscale (per-peer cardinality; peer hostname labels) |
--exporter.enable-tor |
OPNSENSE_EXPORTER_ENABLE_TOR |
false |
Enable the Tor circuit/stream telemetry collector (control-port GETINFO via the os-tor plugin). Off by default: each scheduled poll does two extra configd execs to query the control port, and requires the plugin's control port + password to be configured. Silent when the os-tor plugin is absent. |
--exporter.enable-unbound-infra |
OPNSENSE_EXPORTER_ENABLE_UNBOUND_INFRA |
false |
Enable per-upstream infra cache RTT metrics from Unbound (cardinality scales with the resolver's infra cache; one series pair per upstream ip/host) |
--exporter.enable-unbound-qstats |
OPNSENSE_EXPORTER_ENABLE_UNBOUND_QSTATS |
false |
Enable Unbound DNSBL query-stats totals and blocklist size metrics, plus local-zone/data/insecure-domain counts. Off by default: the query-stats totals call is backed by an expensive configd+python+pandas+DuckDB query (~1s per scheduled poll) - skipped entirely while query-stats logging (general.stats) is off on the box, but still paid for on every scheduled poll once it is on. |
--exporter.enable-vnstat |
OPNSENSE_EXPORTER_ENABLE_VNSTAT |
false |
Enable the vnstat persistent traffic accounting collector (day/month/total bytes per interface, survives reboots). Off by default: each scheduled poll does one interface_list call plus one get_json_data call per interface vnstat tracks. Silent when the os-vnstat plugin is absent. |
--exporter.firmware-cache-ttl |
OPNSENSE_EXPORTER_FIRMWARE_CACHE_TTL |
12h0m0s |
How long to cache firmware API responses (status and, when enabled, package details). The firmware data OPNsense serves is the stored result of the box's own update check, which it refreshes roughly daily, so re-fetching it on every poll only costs firewall CPU. Set to 0 to fetch on every poll. |
--exporter.ids-alert-lookback |
OPNSENSE_EXPORTER_IDS_ALERT_LOOKBACK |
15m |
Lookback window over which opnsense_ids_recent_alerts counts Suricata eve alerts (a gauge). Only used when --exporter.enable-ids-alerts is set. Counts are a floor when more than 500 alerts fall inside the window. |
--exporter.instance-label |
OPNSENSE_EXPORTER_INSTANCE_LABEL |
-- | Label to use to identify the instance in every metric. If you have multiple instances of the exporter, you can differentiate them by using different value in this flag, that represents the instance of the target OPNsense. If left empty, it defaults to the configured OPNsense address (deterministic). Set --exporter.instance-use-hostname to derive it from the OPNsense hostname instead. |
--exporter.instance-use-hostname |
OPNSENSE_EXPORTER_INSTANCE_USE_HOSTNAME |
false |
When --exporter.instance-label is empty, derive the instance label from the OPNsense hostname reported by the API instead of the configured address. This lookup is deterministic: it blocks at startup and, if the hostname cannot be obtained, the exporter refuses to start (rather than silently falling back to the address, which would make the label depend on startup timing and flip between restarts). |
--exporter.max-scrape-duration |
OPNSENSE_EXPORTER_MAX_SCRAPE_DURATION |
50s |
Upper bound on a single collector poll (#336). Since serving /metrics now replays an in-memory snapshot rather than calling the API, this bounds each background poll so a stalled/blackholed endpoint frees its poll-concurrency slot instead of holding it open. Serving itself is never blocked by it. |
--exporter.series-budget |
OPNSENSE_EXPORTER_SERIES_BUDGET |
100000 |
Soft budget for the total number of Prometheus series produced by the COLLECTOR registry (the same set /metrics and the OTLP bridge serve, and what metricsnap replays to the web UI's /cardinality report) — this is NOT the exporter process's full series count: process_/go_ self-metrics and the opnsense_exporter_otlp_* delivery-health family live on a separate self registry and are never counted here, so this number will read lower than what your Prometheus tenant ultimately stores for this job. Nothing is ever dropped, capped or refused when it is exceeded (#494) — exceeding it only logs a rate-limited warning (once on the transition into the over-budget state, then at most hourly while it persists, and once more on the transition back under budget) and is reported on /cardinality alongside the existing per-metric warn/crit thresholds, which are a different, unrelated dimension. Set to 0 to disable the check entirely. |
--flow.correlate |
OPNSENSE_EXPORTER_FLOW_CORRELATE |
true |
Correlate NetFlow fragments and Zenarmor conn documents into one merged flow record per connection-window. A pass-through when only one source is present. Off emits NetFlow records raw and per-fragment. |
--flow.correlate.max-entries |
OPNSENSE_EXPORTER_FLOW_CORRELATE_MAX_ENTRIES |
50000 |
Hard cap on live correlator entries. At the cap the oldest is force-emitted (never dropped) and counted. The NetFlow ingress is unauthenticated, so this bounds memory against a flood. 0 is unbounded (unwise with the listener on). |
--flow.correlate.window |
OPNSENSE_EXPORTER_FLOW_CORRELATE_WINDOW |
3m |
How long the correlator holds a connection-window before emitting. Also the maximum a flow log is delayed. NetFlow export lag runs to ~30m for long flows (#346), so a flow whose records straddle the window emits a partial per window rather than one joined record. |
--flow.dns-cache.size |
OPNSENSE_EXPORTER_FLOW_DNS_CACHE_SIZE |
50000 |
Entries in the DNS answer cache that gives a flow to a bare IP its dst.domain, fed by the Zenarmor dns family. Over the cap it stops inserting rather than evicting hot entries. 0 disables domain enrichment. |
--flow.enabled |
OPNSENSE_EXPORTER_FLOW_ENABLED |
true |
Enable flow rollups: bounded byte and packet volume counters derived from flow records. Costs nothing where no flow source is configured - the metrics are simply silent, like log_events without the syslog receiver. Set --exporter.disable-flow to remove the collector entirely. |
--flow.geoip.metric-dims |
OPNSENSE_EXPORTER_FLOW_GEOIP_METRIC_DIMS |
false |
Add a country label to the flow volume metrics. OFF by default and it should usually stay off: country is a ~250-value dimension multiplied against every existing flow series, so turning it on can multiply opnsense_flow_bytes_total's cardinality roughly 250-fold. --flow.top-n and --flow.max-keys still bound the result, which means the practical effect on a busy box is that real series start folding into other rather than that the family grows without limit. ASN and city NEVER become labels at any setting. Geo on flow LOGS needs no flag - it is unconditional whenever --geoip.enabled is set. |
--flow.log-mode |
OPNSENSE_EXPORTER_FLOW_LOG_MODE |
per_flow |
Flow log emission: "per_flow" ships one OTLP log record per correlated flow on the shared log pipeline; "off" ships none while still deriving all metrics. Zenarmor conn documents ship on their own lane regardless. |
--flow.max-keys |
OPNSENSE_EXPORTER_FLOW_MAX_KEYS |
2500 |
Maximum distinct label combinations the flow accumulator tracks in memory. A separate bound from --flow.top-n: this caps memory between scrapes, that caps emitted series. Combinations first seen at the cap fold into other and are counted by opnsense_flow_rollup_capped_total. 0 is unbounded. |
--flow.max-logs-per-window |
OPNSENSE_EXPORTER_FLOW_MAX_LOGS_PER_WINDOW |
0 |
Cap on flow log records shipped per minute; excess is TRUNCATED (never sampled) and counted. A flood guard on the unauthenticated NetFlow ingress. 0 is unlimited. Metrics are never truncated. |
--flow.netflow.allowed-peers |
OPNSENSE_EXPORTER_FLOW_NETFLOW_ALLOWED_PEERS |
-- | CIDR allowlist of exporters permitted to send flow records, repeatable. Empty means accept from anyone, which is a deliberate decision to trust the network rather than a default to drift into: anything that can reach the port can inject flow records. |
--flow.netflow.debug-capture |
OPNSENSE_EXPORTER_FLOW_NETFLOW_DEBUG_CAPTURE |
off |
Dump raw NetFlow datagrams to --logs.debug-capture.dir. "unidentified" writes only datagrams carrying something the decoder could not interpret (an unmodelled template element, an options template, an unknown flowset, or a datagram that would not decode at all) - cheap, and the mode worth leaving on. "all" writes every datagram, for regenerating a replay fixture or measuring the export; deliberately heavy, bounded only by --logs.debug-capture.max-bytes. Requires --flow.netflow.enabled and the shared dir. |
--flow.netflow.enabled |
OPNSENSE_EXPORTER_FLOW_NETFLOW_ENABLED |
false |
Enable the NetFlow v5/v9 receiver. Opens an UNAUTHENTICATED UDP socket: NetFlow has no authentication of any kind, so restrict it with --flow.netflow.allowed-peers or by firewalling the port. Requires --flow.enabled. |
--flow.netflow.ifindex-map |
OPNSENSE_EXPORTER_FLOW_NETFLOW_IFINDEX_MAP |
-- | Override the derived NetFlow ifIndex-to-device map, as comma-separated index=device pairs (e.g. "1=ixl0,5=igb0,13=ixl0_vlan50"). Entries listed here beat the derived map; indices not listed still use it, so pin every index that carries traffic. Read yours off the box with: ifinfo | awk '$1 == "Interface" { n++; print n, $2 }' - that is the whole enumeration. ngctl list | grep netflow shows only the interfaces netflow captures, and an egress index can legitimately name one it does not. A pin is a STATIC assertion against a POSITIONAL index: adding or removing any interface renumbers every position above it, so a pin that was right when written silently goes stale and then actively mislabels, because it still wins. Re-read the enumeration after any interface change and watch opnsense_flow_ifindex_conflicts, whose reason="derived_differs" is that divergence; settle which side is right with ngctl show netflow_ |
--flow.netflow.listen |
OPNSENSE_EXPORTER_FLOW_NETFLOW_LISTEN |
:2055 |
Address the NetFlow receiver binds, host:port. Bound eagerly at startup, so a port already in use is a startup error rather than a receiver that is silently never there. |
--flow.top-n |
OPNSENSE_EXPORTER_FLOW_TOP_N |
1000 |
Maximum flow series emitted per scrape. Everything beyond folds into a single other series per source, so the family still sums exactly at any limit. 0 emits every tracked combination. |
--flow.top-talkers |
OPNSENSE_EXPORTER_FLOW_TOP_TALKERS |
false |
Emit opnsense_flow_top_talker_bytes_total: bytes per internal host and direction, top-N with an other remainder. OFF by default because the host label is high cardinality; the top-N bounds it but a host label is still one series per host. |
--flow.zenarmor |
OPNSENSE_EXPORTER_FLOW_ZENARMOR |
true |
Derive flow records from the Zenarmor receiver's conn documents. Adds no new log records to Loki: the conn document ships exactly as before and this only feeds the metric rollup. Requires --logs.zenarmor.enabled to produce anything. |
--geoip.asn-database |
OPNSENSE_EXPORTER_GEOIP_ASN_DATABASE |
-- | Path to a MaxMind GeoLite2-ASN / GeoIP2-ISP database. This is the one enrichment no amount of Zenarmor coverage supplies: Zenarmor ships no ASN database on any box. Defaults to the downloaded copy when --geoip.download.enabled is set. |
--geoip.country-database |
OPNSENSE_EXPORTER_GEOIP_COUNTRY_DATABASE |
-- | Path to a MaxMind Country OR City database (GeoLite2-Country, GeoLite2-City, GeoIP2-City). A City database is a strict superset, so one path accepts either and the city/region attributes are simply absent with a Country file. Defaults to the downloaded copy when --geoip.download.enabled is set. A missing file is not an error - enrichment is fail-open and the attributes are just absent. |
--geoip.download.account-id |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_ACCOUNT_ID |
-- | MaxMind account ID for the database download API (the Basic-auth username). |
--geoip.download.dir |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_DIR |
/var/lib/opnsense-exporter/geoip |
Directory downloaded databases are installed into, as |
--geoip.download.editions |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_EDITIONS |
GeoLite2-Country,GeoLite2-ASN |
Comma-separated MaxMind edition IDs to download. Default is Country + ASN (~9 MB and ~12 MB resident). Swap GeoLite2-Country for GeoLite2-City (~60 MB resident) to get city and region attributes without Zenarmor - the same --geoip.country-database path accepts either edition. |
--geoip.download.enabled |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_ENABLED |
false |
Download MaxMind databases directly, so no geoipupdate cron or sidecar is needed. Requires --geoip.download.account-id and a license key. Conditional requests mean an unchanged database costs a 304 and no download quota. Off by default: operator-managed files are the supported baseline and this adds an outbound network dependency. |
--geoip.download.interval |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_INTERVAL |
24h |
How often to ask MaxMind for a newer build. GeoLite2 rebuilds twice a week, so daily is ample; an unchanged database answers 304 and costs no quota. The first download runs at startup regardless, so a fresh container is not blind for a whole interval. 0 downloads only at startup. |
--geoip.download.license-key |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_LICENSE_KEY |
-- | MaxMind license key. This flag/ENV or OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_LICENSE_KEY_FILE may be set; the file form is preferred for a container secret. |
--geoip.download.timeout |
OPNSENSE_EXPORTER_GEOIP_DOWNLOAD_TIMEOUT |
5m |
End-to-end timeout for one edition's download. A timeout leaves the installed database untouched and is retried on the next interval. |
--geoip.enabled |
OPNSENSE_EXPORTER_GEOIP_ENABLED |
false |
Enable local GeoIP enrichment of flow records from MaxMind .mmdb files on disk. Adds country/continent/city/ASN attributes to flow LOGS for external addresses, so geo no longer depends on whether Zenarmor happened to see the connection. Purely local: no lookup ever touches the network. Off by default because it needs a database the exporter does not ship. |
--geoip.reload-interval |
OPNSENSE_EXPORTER_GEOIP_RELOAD_INTERVAL |
15m |
How often to re-stat the database paths and hot-swap a changed file. This is what makes the operator-managed path work - a geoipupdate cron, a sidecar or a re-mounted volume can rewrite the files under a running exporter. Separate from --geoip.download.interval, which asks MaxMind whether a newer build exists. 0 disables reloading. |
--log.format |
-- | logfmt |
Output format of log messages. One of: [logfmt, json] |
--log.level |
-- | info |
Only log messages with the given severity or above. One of: [debug, info, warn, error] |
--logs.batch-max |
OPNSENSE_EXPORTER_LOGS_BATCH_MAX |
5000 |
Maximum number of records the emitter hands to the sink per batch. The sink pays a fixed per-resource-partition round-trip, and distinct partitions plateau with batch duration, so a larger batch amortises that fixed cost almost linearly rather than costing proportionally more. |
--logs.buffer-max-bytes |
OPNSENSE_EXPORTER_LOGS_BUFFER_MAX_BYTES |
134217728 |
Aggregate byte budget for the in-memory backpressure queue. The record-count cap (--logs.buffer-size) alone does not bound memory: a receiver preserves each record's raw body, so a few large records can outweigh thousands of small ones. On overflow the oldest record is dropped and counted, exactly as for the count cap. 0 disables the byte budget. |
--logs.buffer-size |
OPNSENSE_EXPORTER_LOGS_BUFFER_SIZE |
65536 |
Capacity of the in-memory backpressure queue between pollers and the sink. On overflow the oldest record is dropped and counted (logs_dropped_total). At the measured ~475 bytes/record retained size, 65536 records is ~31MB, comfortably under the 128MiB --logs.buffer-max-bytes default, so the two bounds read against one number instead of this record cap silently binding first at a fraction of the byte budget. |
--logs.crowdsec.enabled |
OPNSENSE_EXPORTER_LOGS_CROWDSEC_ENABLED |
false |
Enable the crowdsec log source: ships CrowdSec alert and decision records to Loki (there is no native syslog path for these - the plugin registers no syslog scope; alerts live only in the LAPI). Requires --logs.enabled. Polls at a 60s floor regardless of --logs.poll-interval. Silent when the os-crowdsec plugin is absent. Off by default. |
--logs.debug-capture.dir |
OPNSENSE_EXPORTER_LOGS_DEBUG_CAPTURE_DIR |
-- | Directory to dump UNMODELLED receiver signals into for inspection, as NDJSON under |
--logs.debug-capture.max-bytes |
OPNSENSE_EXPORTER_LOGS_DEBUG_CAPTURE_MAX_BYTES |
256MiB |
Total size cap for --logs.debug-capture.dir (e.g. 256MiB, 1GB). Capture STOPS when the dir reaches this, keeping the oldest samples; it never deletes to make room, so a debug capture can never fill the disk. Counts bytes left by previous runs. |
--logs.enabled |
OPNSENSE_EXPORTER_LOGS_ENABLED |
false |
Enable the opt-in log/event shipping pipeline (polls OPNsense event APIs and ships to Loki via OTLP). Off by default. Independent of --otlp.enabled (which gates metrics). |
--logs.ids.enabled |
OPNSENSE_EXPORTER_LOGS_IDS_ENABLED |
false |
Enable the IDS (Suricata EVE alert) log source: ships full Suricata alert records polled via ids/service/query_alerts. Off by default. Requires --logs.enabled. If the box already forwards EVE JSON via syslog (ids.general.syslog_eve), prefer that native path instead of also enabling this source - do not ship the same alerts twice. |
--logs.max-metric-keys |
OPNSENSE_EXPORTER_LOGS_MAX_METRIC_KEYS |
5000 |
Maximum distinct label tuples retained per derived log_events metric family. Receivers are push-based and syslog over UDP has a spoofable source, so tuple values are sender-controlled: without this bound a sender can grow process-lifetime metric state without limit. Tuples beyond the cap fold into a counted overflow series rather than being dropped silently. 0 disables the cap. |
--logs.max-record-bytes |
OPNSENSE_EXPORTER_LOGS_MAX_RECORD_BYTES |
1048576 |
Maximum estimated retained size for a single record - its body, source and attributes plus a fixed overhead allowance, measured the same way as --logs.buffer-max-bytes so the two read against one number. A record larger than this is rejected at ingest and counted rather than queued, so one oversized record cannot occupy the whole queue budget or become a batch the sink permanently refuses. 0 disables the per-record cap. |
--logs.poll-interval |
OPNSENSE_EXPORTER_LOGS_POLL_INTERVAL |
10s |
Base interval between event polls per source (floor 5s). Sources may raise their own floor. |
--logs.ship-concurrency |
OPNSENSE_EXPORTER_LOGS_SHIP_CONCURRENCY |
8 |
Maximum number of resource partitions within one batch that the sink exports concurrently. Each partition is a separate synchronous wire request, so a batch of N partitions previously cost N sequential round-trips. 1 restores the old fully-sequential behaviour. Values below 1 are normalised to 1. |
--logs.ship-max-attempts |
OPNSENSE_EXPORTER_LOGS_SHIP_MAX_ATTEMPTS |
10 |
Maximum delivery attempts for one batch before it is dropped and counted (logs_dropped_total{reason="ship_failed_permanent"}). Retries are exponentially backed off. Without this bound a batch the sink permanently refuses is retried forever by the single emitter goroutine, wedging all subsequent delivery. 0 restores unlimited retries. |
--logs.sink |
OPNSENSE_EXPORTER_LOGS_SINK |
otlp |
Log shipping sink: otlp (OTLP logs, reuses the --otlp.* transport) or stdout (one JSON line per event). |
--logs.state-file |
OPNSENSE_EXPORTER_LOGS_STATE_FILE |
-- | Optional path to persist per-source cursors across restarts (atomic JSON). Empty = in-memory only (resume from now on restart). |
--logs.syslog.allowed-peers |
OPNSENSE_EXPORTER_LOGS_SYSLOG_ALLOWED_PEERS |
-- | Comma-separated CIDR allowlist of hosts permitted to send syslog (e.g. 10.0.0.254/32). Empty accepts any sender. Syslog is unauthenticated, so set this on a shared network. |
--logs.syslog.debug-capture |
OPNSENSE_EXPORTER_LOGS_SYSLOG_DEBUG_CAPTURE |
false |
Dump syslog lines this receiver cannot parse (unknown program, no matching parser, or an unparseable envelope) to --logs.debug-capture.dir for inspection. Requires --logs.debug-capture.dir. Additive - these lines still ship as generic records. |
--logs.syslog.enabled |
OPNSENSE_EXPORTER_LOGS_SYSLOG_ENABLED |
false |
Enable the syslog receiver: listens for logs pushed by OPNsense (RFC5424 or RFC3164, UDP and/or TCP) and ships them enriched with rule descriptions, interface names and hostnames. Off by default. Requires --logs.enabled. Configure a matching target on the firewall under System > Settings > Logging > Targets. |
--logs.syslog.enrich |
OPNSENSE_EXPORTER_LOGS_SYSLOG_ENRICH |
true |
Enrich received syslog records from the OPNsense API: firewall rule descriptions (including auto-generated system rules), friendly interface names, DHCP hostnames, MAC addresses, local/remote scope and well-known service names. |
--logs.syslog.exclude-programs |
OPNSENSE_EXPORTER_LOGS_SYSLOG_EXCLUDE_PROGRAMS |
-- | Comma-separated syslog programs to DROP (e.g. radvd,cron). Empty ships everything. Dropped records are counted in opnsense_exporter_logs_rejected_total{reason="filtered"} - never silently discarded. |
--logs.syslog.include-programs |
OPNSENSE_EXPORTER_LOGS_SYSLOG_INCLUDE_PROGRAMS |
-- | Comma-separated syslog programs to ship, dropping everything else. Empty ships everything. Mutually exclusive with --logs.syslog.exclude-programs. |
--logs.syslog.listen-tcp |
OPNSENSE_EXPORTER_LOGS_SYSLOG_LISTEN_TCP |
:5514 |
TCP listen address for the syslog receiver. Empty disables the TCP listener. Prefer TCP for firewall logs: UDP datagram loss is silent and unrecoverable. |
--logs.syslog.listen-tls |
OPNSENSE_EXPORTER_LOGS_SYSLOG_LISTEN_TLS |
-- | TLS listen address for the syslog receiver (RFC5424 over TLS, OPNsense tls4/tls6). Empty disables the TLS listener. Requires --logs.syslog.tls-cert-file and --logs.syslog.tls-key-file. |
--logs.syslog.listen-udp |
OPNSENSE_EXPORTER_LOGS_SYSLOG_LISTEN_UDP |
:5514 |
UDP listen address for the syslog receiver. Empty disables the UDP listener. Port 5514 (not 514) because 514 is privileged and the container runs non-root. |
--logs.syslog.max-conns |
OPNSENSE_EXPORTER_LOGS_SYSLOG_MAX_CONNS |
64 |
Maximum concurrent connections to the syslog receiver, applied PER TRANSPORT: plain TCP and TLS each get this budget from a separate pool. They are separate so a plaintext flood cannot starve authenticated mTLS senders out of the capacity they need. Bounds goroutine growth on an unauthenticated ingress; with both transports enabled the worst-case connection count is twice this value. |
--logs.syslog.min-severity |
OPNSENSE_EXPORTER_LOGS_SYSLOG_MIN_SEVERITY |
-- | Drop records less severe than this (emerg, alert, crit, err, warning, notice, info, debug). E.g. notice drops info and debug. Empty ships every severity. |
--logs.syslog.sample |
OPNSENSE_EXPORTER_LOGS_SYSLOG_SAMPLE |
false |
Sample (drop) high-volume raw log lines AFTER their metrics have been derived: keep firewall block/reject lines and drop passes, keep HAProxy state changes and errors and drop the per-connection noise. Low-volume programs (sshd, dhcp, audit, ids) are kept in full. Off by default. Requires the log_events collector (exporter.disable-log-events must not be set) so every dropped line is counted first. |
--logs.syslog.sampled-attribute |
OPNSENSE_EXPORTER_LOGS_SYSLOG_SAMPLED_ATTRIBUTE |
true |
When sampling is on, stamp a sampled="true" attribute on every shipped line so consumers know the log stream is incomplete and must use the derived counters for totals. On by default; only takes effect when --logs.syslog.sample is set. |
--logs.syslog.tls-cert-file |
OPNSENSE_EXPORTER_LOGS_SYSLOG_TLS_CERT_FILE |
-- | PEM server certificate for the TLS syslog listener. |
--logs.syslog.tls-client-ca-file |
OPNSENSE_EXPORTER_LOGS_SYSLOG_TLS_CLIENT_CA_FILE |
-- | PEM CA bundle to verify sender client certificates on the TLS syslog listener. When set, a sender MUST present a certificate signed by this CA - the only real sender authentication syslog offers. Empty accepts any TLS client (encryption only). |
--logs.syslog.tls-key-file |
OPNSENSE_EXPORTER_LOGS_SYSLOG_TLS_KEY_FILE |
-- | PEM private key for the TLS syslog listener. |
--logs.unbound.enabled |
OPNSENSE_EXPORTER_LOGS_UNBOUND_ENABLED |
false |
Enable the opt-in Unbound per-query DNS log source (pi-hole-style query log to Loki: domain, client, action, resolution source, blocklist and dnssec_status per query). Off by default; requires --logs.enabled. CAVEAT: without a per-client filter, Unbound's query-log backend (DuckDB) only ever exposes the newest 1000 rows across the WHOLE resolver - on a firewall sustaining more than roughly 1000 queries between polls, older rows silently fall out of that window before this exporter ever sees them. This is accepted, honestly-counted sampling loss, not a bug: it is tracked via opnsense_exporter_logs_possible_gap_total{source="unbound"}, never silently dropped. Homelab/SMB query volumes are fine; a busy enterprise resolver should not enable this. Also requires Unbound reporting/statistics enabled on the firewall. Poll floor 15s regardless of --logs.poll-interval. |
--logs.zenarmor.allowed-peers |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_ALLOWED_PEERS |
-- | Comma-separated CIDR allowlist of hosts permitted to stream (e.g. 10.0.0.254/32). Empty accepts any sender. The receiver is unauthenticated unless --logs.zenarmor.auth-user is set, so set this on a shared network. |
--logs.zenarmor.auth-password |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_AUTH_PASSWORD |
-- | Password for --logs.zenarmor.auth-user. |
--logs.zenarmor.auth-user |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_AUTH_USER |
-- | Require HTTP basic auth on the Zenarmor receiver, with this username. Set the same credentials in Zenarmor's streaming settings. Empty disables auth. |
--logs.zenarmor.debug-capture |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_DEBUG_CAPTURE |
false |
Dump Zenarmor signals this receiver does not model (unhandled Elasticsearch endpoints, unknown families, documents that would not parse) to --logs.debug-capture.dir for inspection. Requires --logs.debug-capture.dir. While on, the unhandled-endpoint warning is suppressed - the capture file carries the same signal. |
--logs.zenarmor.drop-self-traffic |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_DROP_SELF_TRAFFIC |
true |
Drop records describing the exporter's own Elasticsearch ingest connection - Zenarmor inspects the link the receiver listens on, so it reports the very connection delivering its records (roughly 15% of all volume, and most of the http family). Matched on the streaming peer's address plus the receiver's listen port, never the destination address, which a containerised exporter cannot know. Set false to keep them; drops are counted as logs_rejected_total{reason="self_traffic"}. |
--logs.zenarmor.enabled |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_ENABLED |
false |
Enable the Zenarmor receiver: poses as an Elasticsearch node so Zenarmor can stream its reporting data (connections, DNS, TLS, HTTP, threat alerts) to the exporter, which ships it enriched over OTLP. Off by default. Requires --logs.enabled. Configure the firewall under Configuration/Zenarmor > Settings > Streaming Data > 'Stream Reporting Data to External Elasticsearch' - NOT the initial wizard's 'Remote Elasticsearch Database', which replaces local reporting irreversibly. |
--logs.zenarmor.enrich |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_ENRICH |
true |
Enrich received Zenarmor records from the OPNsense API: friendly interface names, local/remote scope and well-known service names. Zenarmor resolves hostnames, MACs and device identity itself, so this adds only what it does not already know. |
--logs.zenarmor.exclude |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_EXCLUDE |
-- | Drop Zenarmor records whose FIELD matches REGEX, as FIELD=~REGEX (e.g. 'server_name=~.*.grafana.net'). Repeatable; default off. The field name is validated at startup against the receiver's attribute vocabulary - a typo is a startup error, never a silent no-op. Derived counters are observed BEFORE the drop, so opnsense_log_events_zenarmor_total stays complete; drops are counted as logs_rejected_total{reason="excluded"} and logs_zenarmor_excluded_total{rule}. EXCLUSION IS LOSSY: the derived counters carry no server_name, query or device_name, so an excluded record's forensic detail is gone for good. Prefer a query-time filter unless volume genuinely forces this. Set via env as one rule per LINE. |
--logs.zenarmor.families |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_FAMILIES |
-- | Comma-separated Zenarmor families to ship (conn, dns, tls, http, alert, sip). Empty ships all of them. Prefer restricting this at the Zenarmor end instead - data cut at source never crosses the wire. Zenarmor streams ~2.5-3.3M records/day (~4-6 GB/day of JSON), of which conn is ~61%. |
--logs.zenarmor.listen-http |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_LISTEN_HTTP |
:9200 |
Listen address for the Zenarmor receiver. Point Zenarmor's streaming URI at it. |
--logs.zenarmor.max-concurrent-requests |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_MAX_CONCURRENT_REQUESTS |
8 |
Maximum bulk requests processed concurrently by the Zenarmor receiver. The per-request body limit bounds one request; without this, N simultaneous requests each buffer that full allowance. Excess requests are refused with 503 before a body is read. 0 disables the limit. |
--logs.zenarmor.tls-cert-file |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_TLS_CERT_FILE |
-- | PEM server certificate for the Zenarmor receiver. Set with --logs.zenarmor.tls-key-file to serve HTTPS, and use an https:// URI in Zenarmor's streaming settings. |
--logs.zenarmor.tls-key-file |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_TLS_KEY_FILE |
-- | PEM private key for --logs.zenarmor.tls-cert-file. |
--logs.zenarmor.transport |
OPNSENSE_EXPORTER_LOGS_ZENARMOR_TRANSPORT |
elasticsearch |
How Zenarmor delivers its reporting data: 'elasticsearch' (default) runs the built-in Elasticsearch receiver on --logs.zenarmor.listen-http; 'syslog' ingests it through the shared syslog receiver (requires --logs.syslog.enabled and a business-tier Zenarmor licence). families/exclude/enrich/drop-self-traffic apply to either transport. |
--opnsense.address |
OPNSENSE_EXPORTER_OPS_API |
-- | Required. Hostname or IP address of OPNsense API |
--opnsense.api-key |
OPNSENSE_EXPORTER_OPS_API_KEY |
-- | API key to use to connect to OPNsense API. This flag/ENV or the OPS_API_KEY_FILE may be set. |
--opnsense.api-secret |
OPNSENSE_EXPORTER_OPS_API_SECRET |
-- | API secret to use to connect to OPNsense API. This flag/ENV or the OPS_API_SECRET_FILE may be set. |
--opnsense.insecure |
OPNSENSE_EXPORTER_OPS_INSECURE |
false |
Disable TLS certificate verification |
--opnsense.max-concurrent-requests |
OPNSENSE_EXPORTER_OPS_MAX_CONCURRENT_REQUESTS |
16 |
Maximum number of background OPNsense API requests in flight across all scheduled collector polls, including nested sub-requests. Bounds the simultaneous PHP/configd load on the firewall: lower it (e.g. 4-8) to protect a low-power appliance at the cost of queued or longer polls; raise it to let more independent polls progress concurrently on capable hardware. It does not affect /metrics replay. Must be >= 1. |
--opnsense.max-retries |
OPNSENSE_EXPORTER_OPS_MAX_RETRIES |
3 |
Number of attempts for a failed OPNsense API request (transport errors / retryable 5xx). Worst-case block time is --opnsense.timeout x this value. |
--opnsense.protocol |
OPNSENSE_EXPORTER_OPS_PROTOCOL |
-- | Required. Protocol to use to connect to OPNsense API. One of: [http, https] |
--opnsense.timeout |
OPNSENSE_EXPORTER_OPS_TIMEOUT |
15s |
Per-request HTTP timeout for calls to the OPNsense API. Combined with --opnsense.max-retries this bounds one endpoint attempt sequence inside a background collector poll (timeout x retries). Keep that product below --exporter.max-scrape-duration so the poll deadline, rather than a request retry, remains the outer bound. Prometheus scrape_timeout applies only to replaying /metrics. |
--otlp.enabled |
OPNSENSE_EXPORTER_OTLP_ENABLED |
false |
Enable pushing metrics to an OTLP endpoint (in addition to the /metrics pull endpoint). Off by default. |
--otlp.endpoint |
OPNSENSE_EXPORTER_OTLP_ENDPOINT |
-- | OTLP endpoint URL. When empty, the standard OTEL_EXPORTER_OTLP_ENDPOINT env var is used. |
--otlp.export-interval |
OPNSENSE_EXPORTER_OTLP_EXPORT_INTERVAL |
60s |
Interval between OTLP metric exports (independent of Prometheus scrapes). |
--otlp.fast-export-interval |
OPNSENSE_EXPORTER_OTLP_FAST_EXPORT_INTERVAL |
0s |
Optional second OTLP export lane for fast-tier collectors only (#390). Zero (the default) keeps the single-stream behaviour exactly. When set, fast-tier collectors (gateways, interfaces, protocol, pf_stats, activity, netflow, carp — or whatever --collector.poll-interval-override makes fast) export at this interval while everything else stays on --otlp.export-interval. Must be shorter than --otlp.export-interval. Fast-tier series are a small fraction of the total, so 15s here costs far less than setting --otlp.export-interval=15s for everything. |
--otlp.grafana-cloud-endpoint |
OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_ENDPOINT |
-- | Grafana Cloud OTLP gateway base URL (required when using the Grafana Cloud shortcut). |
--otlp.grafana-cloud-instance-id |
OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_INSTANCE_ID |
-- | Grafana Cloud OTLP instance ID. With --otlp.grafana-cloud-token, synthesizes basic-auth. This flag/ENV or OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_INSTANCE_ID_FILE may be set. |
--otlp.grafana-cloud-token |
OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_TOKEN |
-- | Grafana Cloud Access Policy token. This flag/ENV or OPNSENSE_EXPORTER_OTLP_GRAFANA_CLOUD_TOKEN_FILE may be set. |
--otlp.headers |
OPNSENSE_EXPORTER_OTLP_HEADERS |
-- | OTLP headers as comma-separated key=value pairs (e.g. X-Scope-OrgID=1,Authorization=Bearer x). When set, replaces OTEL_EXPORTER_OTLP_HEADERS entirely; when empty, that env var is used. |
--otlp.insecure |
OPNSENSE_EXPORTER_OTLP_INSECURE |
false |
Disable TLS for the OTLP connection (plaintext). |
--otlp.protocol |
OPNSENSE_EXPORTER_OTLP_PROTOCOL |
http/protobuf |
OTLP transport protocol: grpc or http/protobuf. Defaults to http/protobuf; an empty value is rejected. |
--otlp.service-name |
OPNSENSE_EXPORTER_OTLP_SERVICE_NAME |
opnsense-exporter |
service.name resource attribute for exported metrics. |
--otlp.tls-ca-file |
OPNSENSE_EXPORTER_OTLP_TLS_CA_FILE |
-- | Path to a CA certificate file used to verify the OTLP server. |
--otlp.tls-cert-file |
OPNSENSE_EXPORTER_OTLP_TLS_CERT_FILE |
-- | Path to a client certificate file for OTLP mutual TLS (requires --otlp.tls-key-file). |
--otlp.tls-key-file |
OPNSENSE_EXPORTER_OTLP_TLS_KEY_FILE |
-- | Path to a client key file for OTLP mutual TLS (requires --otlp.tls-cert-file). |
--pyroscope.application-name |
OPNSENSE_EXPORTER_PYROSCOPE_APPLICATION_NAME |
opnsense-exporter |
Pyroscope application name profiles are reported under. |
--pyroscope.auth-password |
OPNSENSE_EXPORTER_PYROSCOPE_AUTH_PASSWORD |
-- | HTTP basic auth password for Pyroscope (Grafana Cloud Access Policy token). This flag/ENV or PYROSCOPE_AUTH_PASSWORD_FILE may be set. |
--pyroscope.auth-user |
OPNSENSE_EXPORTER_PYROSCOPE_AUTH_USER |
-- | HTTP basic auth user for Pyroscope (Grafana Cloud stack/instance ID). This flag/ENV or PYROSCOPE_AUTH_USER_FILE may be set. |
--pyroscope.disable-mutex-block |
OPNSENSE_EXPORTER_PYROSCOPE_DISABLE_MUTEX_BLOCK |
false |
Disable mutex/block contention profiling. On by default; disabling drops the two contention profiles and their process-global sampling rates. CPU, memory, goroutine (and goroutine-leak, when built with the experiment) profiling are unaffected. |
--pyroscope.server-address |
OPNSENSE_EXPORTER_PYROSCOPE_SERVER_ADDRESS |
-- | Grafana Cloud Pyroscope endpoint URL. When empty, continuous profiling is disabled. |
--pyroscope.tenant-id |
OPNSENSE_EXPORTER_PYROSCOPE_TENANT_ID |
-- | Pyroscope tenant ID (only needed for multi-tenancy; unused for Grafana Cloud). |
--web.config.file |
-- | -- | Path to configuration file that can enable TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md |
--web.disable-exporter-metrics |
OPNSENSE_EXPORTER_DISABLE_EXPORTER_METRICS |
-- | Exclude metrics about the exporter itself (process_, go_). |
--web.listen-address |
-- | :8080 |
Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. Examples: :9100 or [::1]:9100 for http, vsock://:9100 for vsock |
--web.systemd-socket |
-- | -- | Use systemd socket activation listeners instead of port listeners (Linux only). |
--web.telemetry-path |
OPNSENSE_EXPORTER_WEB_TELEMETRY_PATH |
/metrics |
Path under which to expose metrics. |
--web.ui-disable-config |
OPNSENSE_EXPORTER_WEB_UI_DISABLE_CONFIG |
false |
Hide the /config page. |
--web.ui-disable-devices |
OPNSENSE_EXPORTER_WEB_UI_DISABLE_DEVICES |
false |
Hide the /devices page (exposes MAC/hostname). |
--web.ui-enabled |
OPNSENSE_EXPORTER_WEB_UI_ENABLED |
true |
Serve the operator console at / (else the minimal landing page). |
--web.ui-refresh-interval |
OPNSENSE_EXPORTER_WEB_UI_REFRESH_INTERVAL |
5s |
Live-poll interval for the console's dynamic pages. |