Сразу обозначу развилку 2026 года, потому что от неё зависит весь твой стек мониторинга. Ванильный nginx из коробки отдаёт смешные семь цифр через stub_status - и всё. Чтобы получить нормальные nginx метрики, тебе нужна обвязка: экспортер, который парсит эту страничку и переводит в формат Prometheus. А вот Angie (форк nginx от бывшей команды разработки) отдаёт детальные метрики НАТИВНО - REST API с JSON, готовый Prometheus-эндпоинт и визуальную панель. Это не маркетинг, это реально меняет архитектуру наблюдаемости. Разберём оба пути.
nginx stub_status: семь цифр, которые надо понимать
Модуль ngx_http_stub_status_module собран в любой стандартной сборке nginx. Включается одной директивой stub_status внутри location. В нашем эталонном проекте ngx-trace-gateway это вынесено в отдельный служебный сервер на порту 8080 - и это правильный паттерн, который ты должен копировать. Вот глобальный конфиг метрик из эталона:
Код: Выделить всё
server {
listen 0.0.0.0:8080;
server_name localhost;
access_log off;
error_log /var/log/nginx/metrics.error.log;
location = /nginx_status {
stub_status;
access_log off;
allow 127.0.0.1;
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
}
}
Теперь сам вывод. Дёрнем страницу:
Код: Выделить всё
$ curl -s http://127.0.0.1:8080/nginx_status
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
- Active connections: 291 - текущее число активных клиентских соединений, ВКЛЮЧАЯ waiting (keep-alive). Это не число пользователей: один браузер открывает несколько соединений на одну страницу.
- accepts (16630948) - сколько соединений nginx принял ВСЕГО с момента старта. Счётчик только растёт.
- handled (16630948) - сколько обработал. В норме равен accepts. Если handled МЕНЬШЕ accepts - значит, nginx упёрся в лимит (worker_connections исчерпан или ulimit), и часть соединений он принял, но дропнул. Расхождение этих двух чисел - это красный флаг, его обязательно надо вынести в алерт.
- requests (31070465) - сколько HTTP-запросов всего. Больше handled, потому что по одному keep-alive соединению проходит много запросов. Делишь дельту requests на дельту времени - получаешь RPS.
- Reading: 6 - nginx читает заголовки запроса от клиента.
- Writing: 179 - nginx обрабатывает запрос или пишет ответ (включая ожидание апстрима).
- Waiting: 106 - простаивающие keep-alive соединения. Равно Active минус Reading минус Writing. Зависит от keepalive_timeout.

nginx prometheus exporter: мост от семи цифр к дашборду
Prometheus не умеет читать текстовый формат stub_status - ему нужен свой формат метрик (строки вида metric_name{label="x"} value). Связующее звено - официальный nginx-prometheus-exporter от nginx/F5. Это маленький Go-бинарник: ходит на твой /nginx_status, парсит семь цифр и отдаёт их по своему порту 9113 в формате Prometheus.
Запуск в docker-compose рядом с нашим gateway выглядит так:
Код: Выделить всё
nginx-exporter:
image: nginx/nginx-prometheus-exporter:1.5.1
command:
- --nginx.scrape-uri=http://openresty:8080/nginx_status
ports:
- "127.0.0.1:9113:9113"
restart: unless-stopped
Код: Выделить всё
$ curl -s http://127.0.0.1:9113/metrics | grep '^nginx'
nginx_connections_accepted 1.6630948e+07
nginx_connections_active 291
nginx_connections_handled 1.6630948e+07
nginx_connections_reading 6
nginx_connections_waiting 106
nginx_connections_writing 179
nginx_http_requests_total 3.1070465e+07
nginx_up 1
Код: Выделить всё
rate(nginx_http_requests_total[1m])
angie prometheus и REST /status: метрики без экспортера
Здесь начинается то, ради чего многие в 2026 мигрируют с nginx на Angie. Angie отдаёт детальную телеметрию НАТИВНО, тремя способами сразу, без единого стороннего бинарника.
Первое - REST API /status, который возвращает развёрнутый JSON по всему инстансу: соединения, http-зоны, КАЖДЫЙ upstream с состоянием каждого сервера (up/down, число фейлов, время отклика), кэши, резолверы. Конфиг:
Код: Выделить всё
server {
listen 127.0.0.1:8080;
location /status/ {
api /status/;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
}
Код: Выделить всё
$ curl -s http://127.0.0.1:8080/status/http/upstreams/backend/
{
"peers": {
"10.0.0.1:9000": {
"state": "up",
"selected": { "current": 12, "total": 84021 },
"responses": { "200": 80112, "500": 9, "4xx": 233 },
"data": { "sent": 19283746, "received": 882347612 },
"health": { "fails": 0, "unavailable": 0 }
}
}
}
Код: Выделить всё
location =/p8s {
prometheus all;
allow 10.0.0.0/8;
deny all;
}
что реально мониторить и как ловить инциденты
Метрики ради метрик - мусор. nginx мониторинг полезен, когда ты замеряешь то, что предсказывает или объясняет боль пользователя. Вот мой обязательный набор, золотые сигналы для шлюза:
- Доля 5xx (error rate) - сколько процентов ответов это серверные ошибки. Главный сигнал боли. Алерт, когда доля 5xx за 5 минут превышает порог (например 1-2%). На ванильном nginx считается из логов или ngx_otel, на Angie - прямо из responses в /status.
- Latency p95/p99 - не средняя (она лжёт, среднее размазывает выбросы), а 95-й и 99-й перцентиль request_time и upstream_response_time. Если p95 апстрима растёт, а p95 nginx нет - тормозит бэкенд, не шлюз.
- Здоровье апстримов - сколько серверов в пуле в состоянии up. На Angie это поле state в /status плюс активные health checks (бесплатно, в ванильном nginx их нет - только пассивный max_fails). Алерт, если живых серверов меньше N.
- Активные соединения и расхождение accepts/handled - приближение к worker_connections и дропы соединений. Из stub_status напрямую.
- Cache hit ratio - доля ответов из кэша. Наш эталон шлёт заголовок X-Proxy-Cache со значением $upstream_cache_status (HIT/MISS/BYPASS/EXPIRED). Считаешь долю HIT - падение hit ratio бьёт по бэкенду нагрузкой. На Angie cache hit виден в /status по зоне кэша.
типичные грабли
- stub_status торчит наружу. Без allow/deny твоя страница метрик доступна всему интернету, и это утечка инфраструктурной информации (число соединений, RPS - подарок для того, кто планирует DDoS). В эталоне доступ закрыт приватными сетями жёстко. Всегда deny all в конце.
- Скрейпы засоряют логи. Забыл access_log off на location метрик - и Prometheus каждые 15 секунд пишет строку в твой структурированный лог. За сутки это десятки тысяч мусорных записей. Выключай логирование на эндпоинте метрик.
- Ждёшь от stub_status того, чего там нет. Классика: человек поднял exporter и ищет в Grafana latency и 5xx по location. Их там НЕТ и быть не может - stub_status это инстанс-уровень. Нужна разбивка - бери ngx_otel, логи или Angie.
- Считаешь среднюю latency. Avg по латенси скрывает хвосты. Один пользователь ждёт 8 секунд, остальные 50 мс - средняя красивая, а человек страдает. Всегда перцентили.
- Метрики считаются за всё время аптайма. accepts/handled/requests - это счётчики с момента старта nginx. Сырое число бесполезно, всегда бери rate/дельту за окно. Перезагрузил nginx - счётчик не обнулился (reload сохраняет), а вот рестарт обнулит, и rate в Prometheus корректно это переживёт.
- VTS-модуль как первый выбор. Сторонний nginx-module-vts раньше был стандартом для per-location метрик, но в 2026 он во многом вытеснен: на ванильном nginx бери ngx_otel_module (официальный), а если нужны детальные метрики из коробки - просто переходи на Angie, где это нативно.
Соберём цепочку stub_status -> exporter -> Prometheus руками. Понадобится docker и docker-compose.
- Шаг 1. Создай рядом с gateway конфиг метрик из эталона (отдельный server на 8080, location = /nginx_status с stub_status и allow приватных сетей). Проверь конфиг: docker exec -it openresty nginx -t.
- Шаг 2. Изнутри контейнера дёрни страницу: docker exec -it openresty curl -s http://127.0.0.1:8080/nginx_status. Убедись, что видишь Active connections и три счётчика. Если 403 - проверь allow/deny.
- Шаг 3. Добавь сервис nginx/nginx-prometheus-exporter:1.5.1 в docker-compose со scrape-uri на http://openresty:8080/nginx_status, подними его.
- Шаг 4. Проверь экспортер: curl -s http://127.0.0.1:9113/metrics | grep nginx_up. Должно быть nginx_up 1. Если 0 - exporter не достучался до nginx, смотри имя сервиса и порт.
- Шаг 5. Подними Prometheus, в scrape_configs добавь target nginx-exporter:9113. В UI Prometheus выполни запрос rate(nginx_http_requests_total[1m]) - погоняй curl-ом продовый эндпоинт и увидь, как RPS растёт на графике.
- Шаг 6. Подними Grafana, добавь Prometheus как источник, импортируй готовый nginx-prometheus-exporter дашборд по ID из каталога Grafana. Любуйся живыми соединениями и RPS.
- Шаг 7 (бонус). Если есть Angie - подними его вместо nginx, включи location /status/ с api и location с prometheus, и сравни: те же данные, но БЕЗ контейнера-экспортера, плюс per-upstream разбивка, которой у тебя только что не было.
- В выводе stub_status accepts равно 9000, а handled равно 8990. Что это значит и почему это повод для алерта?
- Почему нельзя получить долю ответов 5xx и p95 latency напрямую из stub_status, и какие три способа решают это на nginx и Angie?
- Зачем в конфиге метрик стоит access_log off в двух местах и какой набор allow/deny закрывает эндпоинт от внешнего мира?
- Чем путь Angie (REST /status плюс нативный Prometheus) отличается от связки ванильный nginx плюс nginx-prometheus-exporter по архитектуре и по детальности метрик?
stub_status - это семь цифр инстанс-уровня и фундамент мониторинга на ванильном nginx; чтобы они доехали до Grafana, нужен nginx-prometheus-exporter. Но детальной картины (5xx, latency, здоровье каждого апстрима, cache hit) из stub_status не выжмешь - для этого либо ngx_otel_module, либо переход на Angie, который отдаёт REST /status в JSON, нативный Prometheus и Console Light без единого стороннего бинарника. Мониторь золотые сигналы - долю 5xx, перцентили latency, здоровье пула, соединения, cache ratio - и алерти только на то, что требует действия. Метрики, которые никто не смотрит до инцидента, - это просто диски, забитые числами.
Источники для сверки: nginx.org (ngx_http_stub_status_module), github.com/nginx/nginx-prometheus-exporter, angie.software (console, http_prometheus, /status API).