Monitor Nginx Stats
If you run anything behind Nginx, the web server itself already knows how hard it's working — it just doesn't shout about it. To monitor Nginx stats you tap the small pool of live counters the server keeps: how many connections are open right now, how many are mid-read, how many are mid-write, and how many are sitting idle on keep-alive. Expose those numbers over HTTP, protect the endpoint, hand it to an external checker, and you've got a lightweight window into real traffic without touching a single application log. This page covers what those counters mean, how to publish them safely, and how to watch them from outside the box.
What is Monitor Nginx Stats
Monitoring Nginx stats means reading the runtime metrics Nginx exposes about its own connection handling, then charting them and alerting when they drift out of range. The data comes from a built-in module, ngx_http_stub_status_module, which publishes a handful of live gauges and counters at a URL you choose. Nothing is written to disk, nothing is sampled after the fact — it's the server's current state, read on demand.
Why bother when you already have access logs? Because logs tell you what finished, and stats tell you what's happening. A log line appears after a request completes. The stub status counters show requests still in flight: the ones stuck reading a slow client body, the ones blocked writing to a backend that's dragging. When latency climbs, that live picture is what points at the cause. It's the fastest way to spot connection pile-ups, keep-alive exhaustion, or a worker pool running out of headroom — and it costs the server almost nothing to serve.
How it works
The stub status module keeps a compact set of values in memory and prints them as plain text on request. The classic output looks like this:
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
Each field has a precise meaning, and the four you'll watch most are active, reading, writing, and waiting.
| Field | Type | What it tells you |
|---|---|---|
| Active connections | Gauge | All open client connections, including waiting ones |
| accepts | Counter | Total connections accepted since start |
| handled | Counter | Total handled — a gap below accepts means dropped connections |
| requests | Counter | Total client requests served |
| Reading | Gauge | Connections where Nginx is reading the request header |
| Writing | Gauge | Connections where Nginx is writing the response back |
| Waiting | Gauge | Idle keep-alive connections waiting for the next request |
Counters only ever climb, so you graph their rate — requests per second, say — by taking the difference between two polls and dividing by the seconds between them. That second line of the output, the bare accepts handled requests triple, is where a subtle failure hides: on a healthy server accepts and handled move in lockstep. The instant handled starts trailing accepts, Nginx is dropping connections because it hit a worker or file-descriptor limit — a problem no status code alone would reveal. Gauges, by contrast, are read as-is; nginx active connections is a snapshot, meaningful only the instant you read it, which is why you sample them often rather than averaging them into mush. External uptime tools can't parse Nginx's plain-text format directly, though. The trick that makes this monitorable is turning that output into structured data: expose the same numbers as first-level JSON or XML, and a service like ostr.io reads the numeric fields straight into charts. That's the bridge between an internal debug page and real custom-data monitoring.
Setup
Here's the practical path from a bare server to a JSON endpoint an external checker can poll. Adjust paths to match your distro.
- Confirm the module is compiled in. Run
nginx -V 2>&1 | tr ' ' '\n' | grep stub_status. If you see--with-http_stub_status_module, you're set — it ships in most packaged builds. - Add a status location. In a server block, expose the stub status on an internal path and lock it down so it isn't world-readable:
location /nginx_status { stub_status; auth_basic "nginx status"; auth_basic_user_file /etc/nginx/.htpasswd; allow 127.0.0.1; deny all; } - Create the credentials file so the endpoint requires a login before it hands out numbers:
htpasswd -c /etc/nginx/.htpasswd statususer. (More on securing this in monitoring protected endpoints.) - Reload Nginx and test:
nginx -t && systemctl reload nginx, thencurl -u statususer:secret http://127.0.0.1/nginx_status. - Publish the numbers as JSON. The stub output is plain text, so transform it. The official
njs(nginx JavaScript) module can build a small handler that emits{"active":291,"reading":6,"writing":179,"waiting":106}, or you can front the text endpoint with a tiny script. Keep the fields flat — first-level numeric keys — so a checker can read them without custom parsing. - Point your monitor at the JSON URL with its credentials stored, set a polling frequency, and pick which fields become chart series with alert thresholds.
Two more polls a day at low frequency plus one at high frequency gives you both a trend line and near-real-time coverage without hammering the server.
Screenshot walkthrough

In the ostr.io dashboard, a monitored JSON endpoint shows up as a set of custom-data series rather than a single up/down light. Each numeric field you exposed — active, reading, writing, waiting — gets its own line on the chart, plotted over your chosen window. The panel lets you attach an alert trigger to any series: cross a ceiling on writing connections, or watch active connections flatten at a suspicious plateau, and the check fires. Because the endpoint is polled from outside your infrastructure, the graph keeps filling even during a local incident. Alongside the custom series you still get the standard availability read — response time and status code — so a single endpoint doubles as both a health probe and a live Nginx stats feed. The screenshot shows those parsed fields rendered as time-series, which is exactly what turns raw stub status into something you can act on.

Use cases
Watching these counters pays off in a few very concrete situations:
- Connection pile-ups. A steady climb in active connections with flat request throughput usually means clients or a backend are holding sockets open. Catch it before workers saturate.
- Slow-client detection. A high Reading count points at clients trickling in request headers — sometimes a symptom of a slow-loris style problem, sometimes just mobile networks.
- Backend stalls. Writing creeping upward while your app's response time grows is the fingerprint of an upstream that can't keep pace.
- Keep-alive tuning. The Waiting gauge shows how many idle keep-alive connections you're carrying; if it's huge, your timeouts may be too generous.
- Capacity planning. Trend nginx active connections and requests-per-second over weeks and you can size the next instance from data instead of a guess.
- Dropped connections. Any gap between
acceptsandhandledmeans Nginx hit a resource limit and dropped a connection — a red flag worth an alert on its own.