Skip to main content

NATS

NATS serves JSON monitoring data at its HTTP port (:8222). The prometheus-nats-exporter sidecar converts that into Prometheus text on :7777, and the OpenTelemetry Collector's prometheus receiver scrapes the exporter, collecting 80+ metrics from NATS 2.0+ across message throughput, client connections, slow consumers, JetStream storage, and server resources. This guide configures the exporter and receiver and ships metrics to base14 Scout.

Prerequisites

RequirementMinimumRecommended
NATS Server2.02.10+
Prometheus NATS Exporter0.12.00.15.0+
OTel Collector Contrib0.90.00.153.0
base14 ScoutAny-

Before starting:

  • NATS must be running with the HTTP monitoring port (8222) enabled.
  • JetStream must be enabled (-js) if you want the jetstream_* metrics.
  • The prometheus-nats-exporter must be deployed alongside the server, one per NATS node, with its port (7777) reachable from the host running the Collector.
  • A Scout account and OTLP endpoint.
  • OTel Collector installed - see Docker Compose Setup.

What You'll Monitor

Metrics are grouped into three tiers by how you use them. Scrape Core always, alert on Operational, and reach for Diagnostic during an incident or capacity review.

A few things shape this surface, and they matter for every tier below:

  • The exporter sidecar is mandatory. NATS exposes its monitoring data as JSON at :8222, which the prometheus receiver cannot parse. The prometheus-nats-exporter converts it to Prometheus text - run one per NATS node.
  • up is the liveness signal here. Unlike the native-receiver components, NATS has a real up series: the prometheus receiver emits up = 1 when the exporter scrape succeeds. Note that up tracks exporter reachability; the gnatsd_* values are only fresh while the exporter can reach NATS at :8222.
  • Everything is exposed as a Prometheus gauge. The exporter re-reads the monitoring data each scrape and publishes even cumulative server counters - gnatsd_varz_in_msgs, gnatsd_varz_out_msgs, gnatsd_varz_total_connections - as gauges carrying the running total. Derive throughput with rate() or deltas, and expect a reset to 0 on a server restart.
  • JetStream metrics need -jsz=all on the exporter and -js on the server. The jetstream_* family and the gnatsd_varz_jetstream_stats_* series are absent otherwise.
  • The gnatsd_ prefix is legacy - from when the server binary was gnatsd. The exporter keeps it for dashboard and alert compatibility.

Core - is it up, moving messages, and keeping clients fed

MetricWhat it tells you
upScrape liveness - 1 = the NATS exporter responded. The liveness signal on this surface.
gnatsd_varz_in_msgs, gnatsd_varz_out_msgsMessages received / sent - the headline broker throughput. Cumulative gauges; use rate() for msgs/sec.
gnatsd_varz_connectionsCurrent client connections - load.
gnatsd_varz_slow_consumersClients or routes that fell behind and had messages dropped. NATS's signature health signal; non-zero means back-pressure.

Operational - what to alert on

GroupMetricsWhat it tells you
Bandwidthgnatsd_varz_in_bytes, gnatsd_varz_out_bytesBytes in / out (cumulative gauges) - throughput in bytes.
Connectionsgnatsd_varz_subscriptions, gnatsd_varz_total_connectionsActive subscriptions; connections since start (a reconnect-storm / churn signal).
Server resourcesgnatsd_varz_mem, gnatsd_varz_cpuProcess RSS and CPU - memory and CPU saturation.
Clusteringgnatsd_varz_routes, gnatsd_routez_num_routes, gnatsd_varz_leafnodesCluster routes and leaf-node connections; a missing route signals a partition.
JetStream storagegnatsd_varz_jetstream_stats_storage, gnatsd_varz_jetstream_stats_memoryJetStream bytes on disk / in memory - saturate against the configured max.
JetStream APIgnatsd_varz_jetstream_stats_api_errors, jetstream_server_total_messagesAPI errors (alert when climbing); messages stored across all streams.
Consumer lagjetstream_consumer_num_pending, jetstream_consumer_num_ack_pending, jetstream_consumer_num_redeliveredPer-consumer lag: undelivered, awaiting-ack, and redelivered (processing-failure) messages.
Slow-consumer breakdowngnatsd_varz_slow_consumer_stats_* (by connection type: clients / routes / gateways / leafs)Where the back-pressure is coming from.

Diagnostic - for investigation and tuning

Higher cardinality; reach for these during an incident or capacity review.

GroupRepresentative metricsWhen you reach for it
Per-connection drill-downgnatsd_connz_in_msgs, gnatsd_connz_out_msgs, gnatsd_connz_subscriptions, gnatsd_connz_pending_bytes, gnatsd_connz_num_connectionsFind the specific connection behind a slow consumer or a pending-bytes spike. Reads 0 when no client is connected at scrape time.
Per-stream JetStream internalsjetstream_stream_total_messages, jetstream_stream_total_bytes, jetstream_stream_consumer_count, jetstream_stream_first_seq, jetstream_stream_last_seqStream depth, sequence range, and per-stream limits during a backlog.
Per-consumer internalsjetstream_consumer_delivered_stream_seq, jetstream_consumer_ack_floor_stream_seq, jetstream_consumer_num_waitingDelivery / ack sequence position when diagnosing a stuck consumer.
Account / server capacityjetstream_account_storage_used, jetstream_server_total_streams, jetstream_server_total_consumersJetStream account and server-wide capacity accounting.
Static config constantsgnatsd_varz_max_connections, gnatsd_varz_max_payload, gnatsd_varz_ping_interval (and the rest of the gnatsd_varz_* config values)Context for a limit breach - what the server is configured to allow.
Server identitygnatsd_varz_version, gnatsd_varz_server_idConfirm which server / version a series came from.
Scrape metascrape_duration_seconds, scrape_samples_scrapedReceiver-side scrape health, not from NATS.

Full metric list: run curl -s http://localhost:7777/metrics against the exporter.

Key Alerts to Configure

Threshold guidance for the most useful Core and Operational series. These are starting points; tune them to your workload.

MetricThresholdWhy it matters
up== 0 for > 1mThe exporter scrape failed, or NATS is down behind it. Check the exporter and the NATS monitoring port.
gnatsd_varz_slow_consumers> 0Clients or routes are falling behind and messages are being dropped. Increase client buffers or add consumers; inspect connz?sort=pending.
gnatsd_varz_jetstream_stats_storage / gnatsd_varz_jetstream_config_max_storage> 0.85Streams start rejecting writes at the configured limit - add storage or tighten stream retention.
rate(gnatsd_varz_jetstream_stats_api_errors)> 0JetStream operations are failing - inspect stream / consumer config and the server logs.
jetstream_consumer_num_pendingRising vs baselineA consumer is not keeping up with producers - scale the consumer or check its processing.
gnatsd_varz_memRising toward the host limitThe NATS process is approaching memory pressure - check message backlog and connections.
gnatsd_varz_routesBelow the expected peer countA cluster peer is disconnected - quorum / clustering is degraded. Check the peer and network.

The storage alert is a ratio against the server's own configured limit, and the rest are read states or relative-to-baseline - so they hold regardless of your host size. Set absolute byte thresholds only against your own provisioned capacity.

Access Setup

NATS does not expose Prometheus metrics natively. Enable the HTTP monitoring port, then run the prometheus-nats-exporter as a sidecar that reads it.

1. Enable NATS monitoring

Start NATS with the HTTP monitoring port enabled (add -js for JetStream):

Enable monitoring
# Command-line flags
nats-server -m 8222 -js

# Or in nats-server.conf
# http_port: 8222
# jetstream: enabled

Verify the monitoring endpoint:

Verify NATS
# Health endpoint
curl -s http://localhost:8222/healthz

# Server stats
curl -s http://localhost:8222/varz | head -20

The monitoring port is unauthenticated by default - no credentials are needed for the exporter to read it. In production, restrict access to it at the network layer.

2. Deploy the Prometheus NATS Exporter

The exporter converts the NATS JSON monitoring endpoints into Prometheus-format metrics. Pass the flags for the endpoints you want; -jsz=all is required for JetStream metrics:

Docker exporter
docker run -p 7777:7777 \
natsio/prometheus-nats-exporter:latest \
-varz -connz -routez -jsz=all \
http://nats:8222

Flags control which endpoints are scraped:

  • -varz - server statistics (CPU, memory, messages, connections).
  • -connz - per-connection details.
  • -routez - cluster route metrics.
  • -jsz=all - JetStream streams, consumers, and storage.

Verify the Prometheus endpoint:

Verify exporter
curl -s http://localhost:7777/metrics | head -20

Configuration

The recommended config scrapes the exporter and keeps only the NATS series with a metric_relabel_configs keep filter, dropping the Go-runtime and process noise the exporter also emits:

config/otel-collector.yaml
receivers:
prometheus:
config:
scrape_configs:
- job_name: nats
scrape_interval: 10s
static_configs:
- targets:
- ${env:NATS_EXPORTER_HOST}:7777 # exporter host:port
metric_relabel_configs:
- source_labels: [__name__]
regex: "gnatsd_.*|jetstream_.*"
action: keep

processors:
resource:
attributes:
- key: deployment.environment.name
value: ${env:ENVIRONMENT}
action: upsert
- key: environment
value: ${env:ENVIRONMENT}
action: upsert
- key: service.name
value: ${env:SERVICE_NAME}
action: upsert

batch:
timeout: 10s
send_batch_size: 1024

exporters:
otlphttp/b14:
endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT}
tls:
insecure_skip_verify: true

service:
pipelines:
metrics:
receivers: [prometheus]
processors: [resource, batch]
exporters: [otlphttp/b14]

The up and scrape_* series survive the keep filter - the prometheus receiver synthesizes them after relabeling - so the liveness signal is preserved.

Semconv version note: deployment.environment.name is the current OTel attribute (semantic conventions v1.27+, stable in v1.41.0). Scout's UI filters on the lowercase environment key, so emit it alongside the OTel-native deployment.environment.name. The legacy deployment.environment is still accepted for backward compatibility.

Environment Variables

.env
NATS_EXPORTER_HOST=localhost
ENVIRONMENT=your_environment
SERVICE_NAME=your_service_name
OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-tenant>.base14.io

Verify the Setup

Start the Collector and check for metrics within 60 seconds:

Verify metrics collection
# Check Collector logs for scraped NATS metrics
docker logs otel-collector 2>&1 | grep -i "nats"

# Confirm NATS monitoring is up
curl -s http://localhost:8222/healthz

# Confirm the exporter is serving the metric
curl -s http://localhost:7777/metrics | grep gnatsd_varz_connections

Troubleshooting

Exporter shows zero metrics

Cause: The exporter cannot reach the NATS monitoring port.

Fix:

  1. Verify NATS is running with monitoring enabled: curl http://localhost:8222/varz.
  2. Confirm the NATS URL passed to the exporter is correct.
  3. Check that no firewall blocks access between the exporter and NATS. Remember up tracks the exporter, not NATS - a green up with stale gnatsd_* values means the exporter lost its link to :8222.

JetStream metrics missing

Cause: JetStream is not enabled, or the -jsz flag is not set on the exporter.

Look at: the per-stream jetstream_stream_* and per-consumer jetstream_consumer_* Diagnostic series - they are absent entirely when JetStream is off.

Fix:

  1. Enable JetStream on the server: nats-server -m 8222 -js.
  2. Start the exporter with -jsz=all to include stream and consumer metrics.
  3. Verify: curl http://localhost:8222/jsz.

Slow consumer count increasing

Cause: A client is not consuming fast enough, so the server tracks it as a slow consumer and drops its messages.

Look at: gnatsd_varz_slow_consumers for the trend, the gnatsd_varz_slow_consumer_stats_* breakdown for the connection type, and the per-connection gnatsd_connz_pending_bytes to find the offending client.

Fix:

  1. Inspect per-connection stats: curl http://localhost:8222/connz?sort=pending.
  2. Increase client buffer sizes or add more consumer instances.

No metrics appearing in Scout

Cause: Metrics are collected but not exported.

Fix:

  1. Check Collector logs for export errors: docker logs otel-collector.
  2. Verify OTEL_EXPORTER_OTLP_ENDPOINT is set correctly.
  3. Confirm the pipeline includes both the receiver and the exporter.

FAQ

Does this work with NATS running in Kubernetes?

Yes. Run the prometheus-nats-exporter as a sidecar in the NATS pod, pointing at http://localhost:8222. Set the Collector's targets to the exporter's service DNS (e.g., nats-exporter.default.svc.cluster.local:7777). The Collector can run as a sidecar or a DaemonSet.

How do I monitor a NATS cluster?

Each NATS node needs its own exporter sidecar. Add all exporter endpoints to the scrape config:

config/otel-collector.yaml (cluster)
receivers:
prometheus:
config:
scrape_configs:
- job_name: nats
static_configs:
- targets:
- nats-exporter-1:7777
- nats-exporter-2:7777
- nats-exporter-3:7777

Each node is scraped independently and identified by its instance label.

Why does the exporter use gnatsd_ as the metric prefix?

It is a legacy naming convention from when the NATS server binary was called gnatsd. The exporter keeps the prefix so existing dashboards and alerting rules keep working.

Can I monitor NATS without the exporter sidecar?

No. The native NATS monitoring endpoints (/varz, /connz, /routez, /jsz) return JSON, which the prometheus receiver cannot parse. The prometheus-nats-exporter is required to convert them into Prometheus-format metrics.

What's Next?

Was this page helpful?