---
type: Article
title: "Monitor Nginx Stats"
description: "Tap the Nginx stub_status module for live connection counts — reading, writing, waiting — to spot pile-ups and keep-alive exhaustion before latency climbs."
resource: "https://snmp-monitoring.com/http-monitoring/nginx-stats/"
tags: [http-monitoring, snmp]
timestamp: 2026-07-13T00:00:00Z
---

# 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](/http-monitoring/custom-data-json-xml.md).

## 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.

1. **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.
2. **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:
   ```nginx
   location /nginx_status {
       stub_status;
       auth_basic           "nginx status";
       auth_basic_user_file /etc/nginx/.htpasswd;
       allow 127.0.0.1;
       deny  all;
   }
   ```
3. **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](/http-monitoring/protected-endpoints-auth.md).)
4. **Reload Nginx** and test: `nginx -t && systemctl reload nginx`, then `curl -u statususer:secret http://127.0.0.1/nginx_status`.
5. **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.
6. **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

![ostr.io Custom Data Monitoring dashboard charting nginx active connections, reading, writing and waiting values parsed from a JSON endpoint](https://snmp-monitoring.com/img/http/nginx-custom-data-ui.webp)

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.

![Diagram showing Nginx stub_status output converted to JSON and polled by an external monitor over HTTPS](https://snmp-monitoring.com/img/http-monitoring/nginx-stats-flow.webp)

## 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 `accepts` and `handled` means Nginx hit a resource limit and dropped a connection — a red flag worth an alert on its own.

## FAQ

**What exactly does "Active connections" count?**
Every open client connection Nginx is currently holding, including idle keep-alive ones. So Active roughly equals Reading plus Writing plus Waiting at any instant. It's a live gauge, not a running total.

**Do I need Nginx Plus for this?**
No. The four core numbers — active, reading, writing, waiting — come from the free, open-source `ngx_http_stub_status_module` that's built into most packages. Nginx Plus adds a richer API with per-upstream detail, but the basics are free.

**Why expose the numbers as JSON instead of just monitoring the text page?**
External checkers parse first-level numeric fields from JSON or XML into chart series with alert triggers. The default stub status format is plain text, so converting it is what makes each field independently graphable and alertable.

**Is the nginx stub status endpoint safe to leave open?**
Treat it as sensitive. It leaks traffic volume and can aid reconnaissance, so put it behind basic auth and an IP allowlist, and prefer HTTPS. Never leave it reachable from the public internet without protection.

**How often should I poll it?**
Pair a high-frequency check (under five minutes) for fast alerting with a low-frequency one (a few times a day) for the long trend. That combination keeps load light while still catching sudden spikes.

## Conclusion

To monitor Nginx stats well, you don't need heavy tooling — you need the four counters the server already keeps, published as clean JSON, protected by a login, and polled on a sensible cadence. Active, reading, writing, and waiting together tell you what's happening right now, which is exactly what logs can't. Read from outside your network via [double durability](/monitoring/double-durability.md), those numbers keep flowing even when the server itself is in trouble. [ostr.io Monitoring](https://ostr.io/service/monitoring) turns that JSON feed into charts and real-time email and SMS alerts with zero agents to install. Start from the [HTTP monitoring hub](/http-monitoring/index.md) or the [site home](/index.md) to wire the rest of your checks together.
