# теория · шаг 2 из 5
5xx: 502 против 504
Коротко
502 и 504 путают чаще всех остальных кодов вместе: 502 - разговор не состоялся или оборвался, 504 - nginx ждал и не дождался. А ещё у обоих есть общая граница, о которой почти нигде не пишут.
- Подменить код на 502 или 504 nginx может только до первого байта заголовков
ответа. После - клиент получает 200 с обрезанным телом, и в
access_logникакой ошибки нет. no live upstreamsневозможен, если в группе один сервер: единственный сервер nginx нерабочим не считает.proxy_read_timeoutмеряет паузу между чтениями, а не весь ответ.- Отказ по лимиту пишется на уровне
error, ожидание в очереди - наwarn. Это две разные строки и два разных исхода для пользователя.
Если граница «до первого байта» и разница трёх таймаутов знакомы - листай до «Теперь сам».
Сначала ответь сам
В error_log за час сотня строк:
[error] *20 upstream prematurely closed connection while reading upstream
В access_log за тот же час ни одного 5xx. Все коды 200. Кто врёт?
Никто. Бэкенд успел отдать заголовки, nginx успел переслать их клиенту вместе с кодом 200 - и только потом соединение оборвалось. Поменять уже отправленный код нечем.
Замер на стенде: бэкенд обещает Content-Length: 100, отдаёт 4 байта и рвёт
соединение. В логе:
"GET /api/halfdrop HTTP/1.1" 200 4 rt=0.202 upstream=172.24.0.2:8000 us=200
Код 200, четыре байта из обещанных ста. Пользователь видит битую страницу, мониторинг по кодам ответа - идеальную картину.
На что это похоже
Диспетчер соединяет тебя со специалистом. Пока соединения нет, он может сказать «специалист не отвечает» или «линия занята» - у него есть свои слова для отказа.
Но как только специалист снял трубку и произнёс «здравствуйте», диспетчер отходит в сторону. Если связь оборвётся на третьем слове, ты услышишь тишину, а не «специалист не отвечает»: разговор уже начался, и подменить его нечем.
Первое слово специалиста - это и есть заголовки ответа. До них у nginx есть свои коды, после - только пересылка.
Механизм: точка невозврата
Правило
502 и 504 - это диагноз nginx о попытке поговорить с бэкендом. Нет ответа вовсе - 502; ответ не пришёл вовремя - 504; ответ начался и сломался - код остаётся тот, что уже ушёл.
Разбор: четыре строки за кодом 502
Проверено на стенде, все четыре воспроизводятся:
[error] connect() failed (111: Connection refused) while connecting to upstream
[error] upstream prematurely closed connection while reading response header from upstream
[error] upstream sent too big header while reading response header from upstream
[error] no live upstreams while connecting to upstream
| Строка | Что случилось | Куда смотреть |
|---|---|---|
Connection refused |
на порту никто не слушает | бэкенд не запущен, упал или слушает другой адрес |
prematurely closed ... response header |
бэкенд принял запрос и умер, не начав отвечать | падение воркера, OOM (система убила процесс за нехватку памяти), таймаут внутри приложения |
too big header |
заголовки ответа не влезли в буфер | proxy_buffer_size, а заодно разобраться с раздутыми куками |
no live upstreams |
все серверы группы помечены нерабочими | сначала чинить бэкенды, потом смотреть max_fails |
Сравни вторую строку со строкой из начала урока: они отличаются хвостом.
while reading response header значит «до заголовков» и даёт 502; та же
строка с хвостом while reading upstream значит «после» и оставляет клиенту
код, который уже ушёл.
Отдельная ловушка: бэкенд слушает 127.0.0.1, а nginx стучится на localhost,
который резолвится (превращается в адрес) в ::1 - IPv6-адрес этой же машины. Порт открыт, но не тот стек, и в логе всё то же
Connection refused. Пиши адрес явно.
Разбор: где наивное правило подводит
no live upstreams не бывает при одном сервере. Строка выглядит так, будто
её причина - смерть бэкенда, и она действительно про это. Но замер показывает
другое: группа из одного мёртвого сервера три запроса подряд даёт Connection
refused и ни разу не даёт no live upstreams. nginx не выключает
единственный сервер группы, каким бы мёртвым тот ни был - выключить его значило
бы гарантированно отвечать 502 даже после того, как бэкенд поднялся.
С двумя серверами появляется полная цепочка:
2026/09/10 19:59:03 [error] 8#8: *17 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: shop.local, request: "GET /dead/x HTTP/1.1", upstream: "http://127.0.0.1:9/dead/x", host: "localhost"
2026/09/10 19:59:03 [warn] 8#8: *17 upstream server temporarily disabled while connecting to upstream, client: 127.0.0.1, server: shop.local, request: "GET /dead/x HTTP/1.1", upstream: "http://127.0.0.1:9/dead/x", host: "localhost"
2026/09/10 19:59:03 [error] 8#8: *17 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: shop.local, request: "GET /dead/x HTTP/1.1", upstream: "http://127.0.0.1:10/dead/x", host: "localhost"
2026/09/10 19:59:03 [warn] 8#8: *17 upstream server temporarily disabled while connecting to upstream, client: 127.0.0.1, server: shop.local, request: "GET /dead/x HTTP/1.1", upstream: "http://127.0.0.1:10/dead/x", host: "localhost"
2026/09/10 19:59:03 [error] 8#8: *20 no live upstreams while connecting to upstream, client: 127.0.0.1, server: shop.local, request: "GET /dead/x HTTP/1.1", upstream: "http://dead2/dead/x", host: "localhost"
2026/09/10 19:59:03 [error] 8#8: *21 no live upstreams while connecting to upstream, client: 127.0.0.1, server: shop.local, request: "GET /dead/x HTTP/1.1", upstream: "http://dead2/dead/x", host: "localhost"
Номер после звёздочки - номер соединения. Первый запрос (*17) попробовал оба
сервера - две пары строк - и получил 502; второй и третий застали оба
выключенными и сразу получили no live upstreams. Читается цепочка снизу вверх: no live upstreams - это следствие, а причина - в
строках выше, и там же написано, чем именно серверы не понравились.
Таймаут - это пауза, а не общее время. proxy_read_timeout отсчитывается
заново после каждой порции данных. На стенде бэкенд с proxy_read_timeout 5s
отдавал ответ по кусочку раз в три секунды и уложился в 18 секунд без единой
ошибки. Обратное тоже верно: бэкенд, который молчит шесть секунд и потом отвечает
мгновенно, в тот же таймаут упрётся.
Этап, на котором истекло время, написан в строке:
| Фраза в логе | Директива | Умолчание |
|---|---|---|
while connecting to upstream |
proxy_connect_timeout |
60с (больше 75с поставить нельзя); таймаут на любом из трёх этапов даёт 504 |
while sending request to upstream |
proxy_send_timeout |
60с |
while reading response header from upstream |
proxy_read_timeout |
60с |
Отказ по лимиту и очередь пишутся по-разному. Здесь легко ошибиться, потому что строки похожи, а исход для пользователя противоположный:
limit_req ограничивает частоту запросов с одного адреса; zone "perip" -
имя области памяти, где nginx ведёт счёт, excess - на сколько запрос
превысил разрешённую частоту (подробно - в разделе про
лимитыРазбирается в разделе 12, глава «limit_req и limit_conn»: Как nginx считает частоту).
[error] limiting requests, excess: 0.992 by zone "perip"
[warn] delaying request, excess: 0.991, by zone "perip"
По первой строке клиент получил 503, по второй - 200, подождав. limiting на
уровне error - запрос отвергнут. delaying на уровне warn -
запрос поставлен в очередь и всё-таки выполнен. Если в логе только delaying,
отказов не было вообще, и поднимать лимит незачем: люди просто ждут чуть дольше.
Что ломается без этого
Мониторинг «доля 5xx» показывает ноль, а пользователи видят обрезанные страницы.
Это прямое следствие точки невозврата: пока обрывы случаются после заголовков,
кодов ошибок не будет. Ловить такое приходится иначе - по error_log и по
расхождению $body_bytes_sent с обещанным размером - его бэкенд пишет в
Content-Length, а в лог он попадает переменной $upstream_http_content_length.
Второе: команда сутки чинит max_fails, увидев no live upstreams, вместо того
чтобы прочитать три строки выше и узнать, что бэкенды отвергали соединения.
Зачем это в работе
Три строки, которые надо узнавать с первого взгляда, потому что означают «плохо всем сразу»:
[warn] 9#9: 64 worker_connections are not enough, reusing connections
[alert] 10#10: *10 4 worker_connections are not enough while connecting to upstream
[crit] 10#10: accept4() failed (24: No file descriptors available)
Три строки об одном пределе, и означают они разное - это видно по уровню и по хвосту.
warn с хвостом reusing connections - nginx выкрутился: закрыл
простаивающие keepalive-соединения и обслужил запрос. Замер при
worker_connections 64 под нагрузкой: accepts 120, handled 120 - не потеряно
ни одного соединения. Это счётчики страницы stub_status: сколько соединений
принято и сколько обработано.
alert без «reusing» - слота не нашлось вовсе. Клиент получает 500, а при
сильной нехватке соединения отбрасываются до чтения запроса: замер при
worker_connections 4 дал accepts 38, handled 31, и этих семи в access_log
нет ни строкой.
crit с accept4() - кончились уже дескрипторы процесса, а не слоты nginx.
Текст зависит от библиотеки: на alpine No file descriptors available, на
debian Too many open files, номер ошибки в обоих случаях 24. Лечится
worker_rlimit_nofile плюс лимитом в юните systemd - одного конфига nginx
мало (как поднять лимит в юните, разобрано в уроке про число воркеров). О
расхождении nginx предупреждает ещё при старте; ulimit -n 40 - команда
оболочки, которая ставит процессу предел в 40 открытых файлов, свой смотри
через ulimit -n без числа:
nginx: [warn] 64 worker_connections exceed open file resource limit: 40
Порядок разбора любого 5xx:
- Что в
$upstream_status- бэкенд ответил ошибкой сам или его не спросили? - Что в
error_logв ту же секунду -refused,timed out,prematurely closed? - Есть ли в строке слово
header- оборвалось до заголовков или после? - Это один запрос, один адрес клиента или все сразу?
Поднять таймаут - не починка
Первый рефлекс при 504 - написать proxy_read_timeout 300s;. Иногда это верно
(тяжёлый отчёт, экспорт), но чаще так прячут проблему: пользователь всё равно не
ждёт пять минут, а воркеры и соединения к базе держатся занятыми всё это время, и
под нагрузкой сервис ложится целиком. Сначала посмотри $upstream_header_time в
логах: если он стабильно растёт, лечить надо бэкенд.
Теперь сам
Бэкенд перезапускается на выкладке, это занимает восемь секунд.
proxy_read_timeout - стандартные 60. Какой код увидят пользователи в эти восемь
секунд и что будет в $upstream_response_time?
502, а не 504, и urt=0.000. Ждать нечего: порт закрыт, соединение отвергается
сразу, в логе connect() failed (111: Connection refused). Ждать можно только
того, кто ответил хоть что-то. И если в группе есть второй живой сервер, части
пользователей повезёт: nginx сходит на него, а в строке останется us=502, 200.
Главное
502 - соединения не было или оно оборвалось до заголовков (refused,
prematurely closed ... header, no live upstreams, too big header); 504 -
не дождался, и этап написан в строке. После первого байта заголовков подменить
код нечем: обрыв даёт 200 с обрезанным телом и виден только в error_log.
no live upstreams невозможен при единственном сервере в группе.
proxy_read_timeout меряет паузу между чтениями, а не весь ответ. limiting
requests на уровне error - отказ, delaying request на warn - очередь.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий