12.4 stub_status & Live Metrics
Logs are history; stub_status is now. One location, seven counters, zero dependencies — the OSS answer to "is the front door alive and how hard is it working?"
Advanced
What you will learn
Enabling and reading
stub_status— every line of it.Protecting it (it's an endpoint like any other) and scoping it (why the example below gives it its own server).
What each counter is good for — and the math that turns them into rates.
The honest limits: why you still need logs and a metrics system (and what Plus's extended status adds).
1. The endpoint
server {
listen 9401;
server_name status.example.test;
allow 127.0.0.1; # an endpoint like any other: protect it
deny all;
location = /status {
stub_status;
}
}
stub_status ships in the standard build (
--with-http_stub_status_module — your Lesson 1.3
nginx -V receipt has it) but is off until you declare a location for it. The example above gives it a dedicated
server on its own port — the common production pattern is either that, or a location on the main server ACL'd to the monitoring VLAN. What you should NOT do is expose it publicly: it leaks topology and load levels.
The output (real capture):
Active connections: 1
server accepts handled requests
62 62 62
Reading: 0 Writing: 1 Waiting: 02. Reading it, line by line
Line | Field | Meaning |
|---|---|---|
1 |
| currently open connections (clients + upstreams counted separately — this is client-side) |
2–3 |
| total connections accepted since start |
| total handled (equal to accepts unless resource limits kicked in) | |
| total HTTP requests — the counter your rate math feeds on | |
4 |
| workers currently reading request headers/bodies |
| workers currently returning responses | |
| idle keep-alive connections waiting for the next request |
The
accepts == handled equality is itself a signal: divergence means the kernel accepted connections nginx couldn't serve (listener queue draining,
worker_connections ceiling) — early overload evidence that predates errors.
3. Counters → rates → alerts
All three big numbers are monotonic counters; they only make sense as
rates over time (Prometheus, etc. do this natively with
rate(); a cron'd curl + diff does it in a pinch):
requests: 62 62 62 at T0
requests: 66 66 66 at T0+3s (after 3 probes + 1 status poll)
-> ~1.3 rpsThat's the receipt of the simplest possible check — the counter advanced by exactly the requests sent — which is also your smoke test that the exporter you deploy reads the right thing.
What earns an alert:
Metric | Alert shape |
|---|---|
| trending to
|
| thousands of Waiting + low Writing = mostly-idle keep-alives; fine, unless FD pressure says otherwise |
| slowloris-shaped clients or giant bodies (Module 7 territory) |
request rate collapse | traffic vanished — often the FIRST public symptom of a DNS/cert/edge failure |
4. The honest limits (and Plus)
stub_status is a snapshot of seven numbers. It cannot tell you:
latency — no timers here; that's the log's job (12.3),
per-upstream health — Module 5's error log is richer,
per-route anything — no labels, no dimensions,
history — it's "now," not "the last hour."
The pipeline reality: exporters scrape stub_status every N seconds for the time-series; logs carry the forensic truth; the two corroborate. When the graph says p95 spiked at 14:02, the log lines at 14:02 say which upstream, which URIs, which request_ids. That pairing — coarse continuous metrics + fine discrete logs — is the whole observability architecture.
NGINX Plus's extended status (the Module 1 comparison's "built-in dashboard" row) adds exactly the missing dimensions: per-upstream queues, per-zone traffic, per-server request counts in JSON at
/status. Same philosophy, more labels — and OSS closes the gap the way this module does: logs as data + a metrics scraper, at the cost of one more moving part.
Try it yourself
curl -s http://127.0.0.1:9401/status— poll it, generate some traffic, poll again: the request counter advances with load (62 → 66 in the receipt above).Load it:
for i in $(seq 1 200); do curl -s -o /dev/null http://127.0.0.1:9401/ & done; wait; curl -s http://127.0.0.1:9401/status— watch Active/Reading/Writing move (and Waiting accumulate with keep-alive clients).Break the ACL: change
allow 127.0.0.1to a bogus range, reload, curl — 403. Metrics endpoints get scanned too.
Q&A
Q: Why
stub_status
and not just export /health with sidecar
metrics? A: /health says "alive." stub_status says "alive AND how hard" — capacity signals you want
before the outage. Also: it's built-in, zero-dependency, and every monitoring stack on earth has a collector for it.
Q: Where does QUIC/HTTP/3 traffic show up? A: Same counters — h3 sessions are connections and requests. (QUIC's connection-migration can keep one "connection" alive across network changes; counters still track it as one.)
Q: Multiple workers — is this per-worker? A: The counters are process-global (shared memory), summed across workers.
Active connections likewise. No per-worker split in OSS — Module 16's process tools cover that depth when you truly need it.
Takeaways
stub_status= seven live counters behind one directive; ACL it like any endpoint, scope it to its own server/port.Monotonic counters only matter as rates; request-rate collapse is often the first public symptom of edge failure.
accepts == handleddivergence andActiveapproaching the Lesson 9.1 ceiling are your capacity alarms — visible here, in seven numbers, before any error is thrown.It's a snapshot, not history: metrics (scraped) for continuous signals, logs (12.3) for forensics — the pairing is the architecture.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.