nginx-prometheus-exporter: one scrape target, two NGINX flavours
NGINX Prometheus Exporter for NGINX and NGINX Plus
At a glance
- What is it?
- The official NGINX Prometheus exporter polls a single NGINX or NGINX Plus instance and republishes it on port 9113, turning either the stub_status page or the Plus API into Prometheus metrics. OSS NGINX yields a handful of counters, Plus yields dozens of zones, and v1.5.2 was retracted for a failed signing step.
- Who is it for?
- Adopt this exporter if you run a small number of NGINX or NGINX Plus instances and want their counters in the same Prometheus as everything else, since the binary and container paths are both one command and the exporter-toolkit dependency already handles basic auth and TLS. Hold off if you run hundreds of instances behind one address, because the tool scrapes exactly one target per process, so cardinality and cost scale with the number of processes you are willing to run.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 6, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One NGINX instance in, one metric endpoint out
The exporter's job is narrow and its data flow is short. It fetches metrics from a single NGINX or NGINX Plus instance, converts them into the right Prometheus metric types, and serves them over HTTP for collection. There is no storage, no aggregation and no history in between.
That single-target design is the first thing to internalise, because it determines how you deploy. The README describes exposing the built-in metrics yourself: for OSS NGINX, the stub_status page at /stub_status on port 8080, and for NGINX Plus, the API at /api on port 8080. The exporter then polls whichever you point it at.
The code layout mirrors that flow. exporter.go is the entry point, client/ holds the NGINX-facing HTTP code including the Plus client, and collector/ is where the Prometheus metric families are assembled. The dependency list makes the mechanism visible: github.com/nginx/nginx-plus-go-client/v3 at 3.0.2 for the Plus API, prometheus/client_golang at 1.24.1 for the metric types, and prometheus/exporter-toolkit at 0.20.0, which is the standard library for serving an exporter endpoint with its own TLS and authentication configuration.
The module targets Go 1.25.0, and the build is deliberately static: CGO_ENABLED=0 with -trimpath and linker flags stripping symbols and stamping the version.
Flags, ports and the first run for each flavour
The exporter listens on 9113 by default and serves metrics at /metrics, which is where your Prometheus scrape job should point. Against an OSS NGINX, one command is the whole setup:
docker run -p 9113:9113 nginx/nginx-prometheus-exporter:1.5.1 --nginx.scrape-uri=http://<nginx>:8080/stub_statusThe same thing without Docker is the binary with the identical flag:
nginx-prometheus-exporter --nginx.scrape-uri=http://<nginx>:8080/stub_statusNGINX Plus adds one flag and a different path:
nginx-prometheus-exporter --nginx.plus --nginx.scrape-uri=http://<nginx-plus>:8080/apiThe <nginx> placeholder is whatever IP address or DNS name reaches the instance, which matters when the exporter runs in a container on a different host than NGINX: loopback inside the container is not your NGINX.
Two operational details worth knowing. If you also run NGINX Ingress Controller on Kubernetes, the README points at the ingress controller documentation instead, because that controller ships its own scraping arrangement. And the command-line parser is alecthomas/kingpin, which means the full flag set, including the web configuration flags, is discoverable from the binary's help output rather than from the README alone.
What OSS NGINX actually gives you, and what Plus adds
The metric surface is where the two products diverge sharply, and the README's table of contents is an honest map of it. For OSS NGINX there is one group, the stub status metrics, because the source page exposes a fixed handful of counters: connections including active and idle ones, plus request counters such as accepted, handled and requests.
For NGINX Plus the list runs across many families, and the names are the interesting part. Connections, HTTP, SSL, HTTP server zones, stream server zones, HTTP upstreams, stream upstreams, stream zone sync, location zones, resolver, HTTP requests rate limiting, HTTP connections limiting, stream connections limiting, cache, and worker.
That difference changes what an alert can be. With OSS you can watch connection counts and request volume and little else: no per-upstream queue depth, no rate limiting, no zone state, no cache hit ratios. If you are on OSS and you want those, the exporter cannot invent them, because the data does not exist at the source. Reading a Plus-only metric name as though it were available everywhere is a common and quiet mistake.
Zone-scoped metrics are also why the Plus mode exists as a separate flag rather than a config file: the exporter has to know it is allowed to ask the API for the richer endpoint.
Exposing 9113 without exposing it to everything
The quick start gives you no authentication, and the examples directory is the answer to that. It contains basic_auth/, tls/, kubernetes/ and systemd/ setups, which is the documented way to see how each environment is meant to be wired.
The mechanism is the exporter-toolkit dependency rather than anything hand-written: it provides the web configuration flags for serving over TLS and for requiring basic auth, so an exporter endpoint can be protected with the same conventions as any other Prometheus component. Nothing in the quick start sets those flags, so the default posture of a fresh container is an open metrics endpoint on 9113 wherever you published it.
Two more dependencies shape the network picture. pires/go-proxyproto means the exporter understands the PROXY protocol, so it can sit behind a load balancer that forwards the real client address, which matters for trusted-proxy configuration in NGINX itself. And the collector layer is deliberately separated from the client layer, so you can reason about what fails when: a scrape failure is an NGINX reachability or endpoint problem, not an exposition problem.
Version drift you should know about before pinning
The dependency and build files carry useful detail, and one line in go.mod deserves attention. Near the end of the module file there is a retract directive for v1.5.2, with the reason given as a broken signing step, superseded by v1.5.3. That is an unusually explicit statement: an artefact was published, found to be unusable for verification, and withdrawn in the module graph so that tooling can refuse it.
The second detail is the Makefile, where VERSION is set to 1.5.1 while the module has moved past it. The binary target builds with ldflags stamping that value into version.Version. So a locally built binary reports 1.5.1 whatever the surrounding history says, which matters if you scrape build_info style metrics or triage by version label. Pin the image tag in production and treat a locally built binary's reported version as decorative.
The wider release surface is broad for an exporter: Docker images, binaries, Homebrew, Scoop and Nix, plus documented SBOM and provenance steps for both the binaries and the image. That distribution spread is the practical reason the retract directive exists at all, since a bad artefact can be installed from five places before anyone notices.
Build, test and lint from the Makefile
Building locally is the default make target, and its flags describe a published artefact: a static, trimmed build with the version stamped in through linker flags. The image target passes the version as a build argument to build/Dockerfile, The lint target runs a pinned golangci-lint version through go run. Tests run with the race detector and randomised order, producing an atomic coverage profile:
go test ./... -race -shuffle=on -coverprofile=coverage.txt -covermode=atomicLinting runs golangci-lint through go run at a pinned version rather than from your PATH, so a contributor and CI run the same linter. There is also a deps target that runs go mod tidy, go mod verify and go mod download, and dependency updates are handled by renovate.json, with a renovate comment above the Makefile linter pin keeping the two in step.
Release mechanics are documented in release-process.md, with GoReleaser configured in .goreleaser.yml, which matches the SBOM and provenance sections in the README.
Where this exporter is the wrong tool
The clearest boundary is scale. One process scrapes one NGINX instance. Running this per node behind a Kubernetes DaemonSet is fine, but running it per server block, or pointing one exporter at a rotating list of instances, is not something the tool models. If you want fleet-wide NGINX visibility in a cluster, the ingress controller documentation the README links is the supported path, because that is where an aggregated view is designed to live.
The second boundary is depth on OSS NGINX. If you need per-upstream latency, cache statistics or request timing, the stub_status page does not carry them and no exporter flag changes that. Nginx Plus is the path, or a different observability approach entirely.
The third is operational overhead for small deployments. A single NGINX behind a single host can be scraped by a textfile collector or a handful of curl calls into node_exporter without adding a container, a port and a scrape config. This exporter earns its keep when you have several instances, Plus features, or an environment where a standard endpoint shape matters more than the process count.
Editorial conclusion
Adopt this exporter if you run a small number of NGINX or NGINX Plus instances and want their counters in the same Prometheus as everything else, since the binary and container paths are both one command and the exporter-toolkit dependency already handles basic auth and TLS. Hold off if you run hundreds of instances behind one address, because the tool scrapes exactly one target per process, so cardinality and cost scale with the number of processes you are willing to run. Verify three things first. Pin a version and read go.mod before you do, since v1.5.2 is retracted there with the reason given as a failed signing step, and the current line is 1.5.3. Confirm the scrape URI matches your build, because an OSS instance needs the stub_status page at /stub_status on port 8080 while NGINX Plus needs /api on the same port plus the --nginx.plus flag, and running the wrong one produces an empty metric set rather than an error. And decide where the endpoint lives, since the exporter serves on 9113 with no authentication mentioned in the quick start, and the examples directory ships basic_auth and tls setups that you should copy rather than improvise. Start with docker run -p 9113:9113 nginx/nginx-prometheus-exporter:1.5.1 --nginx.scrape-uri=http://<nginx>:8080/stub_status and confirm /metrics returns something before adding the Prometheus scrape job.
Frequently asked questions
How do I run the NGINX Prometheus exporter?
For OSS NGINX run docker run -p 9113:9113 nginx/nginx-prometheus-exporter:1.5.1 --nginx.scrape-uri=http://<nginx>:8080/stub_status, or run the same flag through the binary. Metrics are then served at /metrics on port 9113.
What is the difference between exporting NGINX and NGINX Plus metrics?
OSS NGINX is scraped from the stub_status page at /stub_status and yields the handful of counters that page exposes. NGINX Plus requires the --nginx.plus flag, scrapes /api, and adds families for zones, upstreams, rate limiting, cache and workers.
How do I secure the exporter endpoint?
The quick start does not set authentication. The repository ships examples under examples/ for basic_auth, tls, kubernetes and systemd, and the prometheus/exporter-toolkit dependency supplies the web configuration flags that back those setups.
Can I use this exporter with NGINX Ingress Controller on Kubernetes?
The README directs Kubernetes users to the NGINX Ingress Controller monitoring documentation rather than to a standalone deployment, because the controller has its own Prometheus integration.
How do I build the exporter from source?
Run the default make target for a static binary, or make container to build the image with the version passed as a build argument. Tests run with go test ./... -race -shuffle=on, and make lint runs a pinned golangci-lint.