Сайт отдаёт 502, 503 или 504: как за пять минут понять, кто ответил ошибкой — nginx, приложение или балансировщик

Сайт отдаёт 502, 503 или 504: как за пять минут понять, кто ответил ошибкой — nginx, приложение или балансировщик

Три похожих кода означают три разные поломки, и от того, какой хоп их вернул, зависит, куда идти чинить.

Пользователь пишет «сайт лежит», в мониторинге красные пятисотые, а в чате спорят: виноват хостинг, разработчики или админ. Спор обычно идёт зря, потому что код ответа уже говорит, на каком участке цепочки сломалось. Цепочка короткая: браузер, затем балансировщик или обратный прокси (программа на входе, которая передаёт запросы дальше, например nginx), затем само приложение. Ниже разбор по RFC 9110 и документации nginx: что значит каждый код и как найти виноватого без гаданий.

Три кода — три разных вопроса к цепочке

Стандарт HTTP вводит 502 и 504 именно для посредника между клиентом и приложением, а 503 описывает как состояние сервера. Пересказываю своими словами:

  • 502 Bad Gateway. Посредник (шлюз или прокси) обратился к следующему серверу и получил от него некорректный ответ. По моему опыту, за этим чаще всего стоит упавшее или не успевшее стартовать приложение.
  • 503 Service Unavailable. Сервер сейчас не может обработать запрос: перегрузка или плановое обслуживание. Стандарт разрешает прикладывать заголовок Retry-After с подсказкой, когда попробовать снова.
  • 504 Gateway Timeout. Посредник не дождался ответа от следующего сервера вовремя. Причина может быть любой: приложение медленное, зависло на чём-то своём (база, внешний API) или недоступно по сети.

Главное различие: 502 — «ответ пришёл, но он некорректный», 504 — «ответа не было слишком долго», 503 — «я сам сейчас не могу». Код ставит тот хоп (участок цепочки), который это увидел, и он не обязан быть тем, кто сломался.

Таймауты nginx: по умолчанию 60 секунд, но не на весь ответ

Если в цепочке стоит nginx, на 504 влияют три настройки из документации модуля proxy: proxy_connect_timeout (установка соединения), proxy_send_timeout (передача запроса) и proxy_read_timeout (чтение ответа). У всех трёх значение по умолчанию 60 секунд. Для connect документация отдельно оговаривает, что на практике он обычно не превышает 75 секунд.

Ловушка в том, что send и read считаются не на весь запрос или ответ, а между двумя соседними операциями записи или чтения. Приложение, которое отдаёт по байту раз в 50 секунд, под таймаут не попадёт, хотя пользователь ждёт минуты. И обратная ситуация: «увеличили таймаут до 300 секунд» лечит только случай, когда приложению правда нужно столько молчать.

Как найти хоп, который вернул ошибку

Самый надёжный источник — журнал nginx с переменными модуля upstream. В формат лога стоит добавить $upstream_addr (адрес сервера, куда ушёл запрос), $upstream_status (код, который ответило приложение) и $upstream_response_time (сколько оно отвечало). Тогда по строке видно четыре случая.

  • Код в логе nginx и $upstream_status совпадают, адрес приложения есть: ошибку вернуло приложение, nginx передал её дальше (если не настроены error_page и перехват ошибок, коды могут расходиться).
  • В логе 502, в $upstream_addr вместо адреса имя группы серверов, а $upstream_status тоже 502: по документации, так бывает, когда сервер выбрать не удалось. Скорее всего, запрос до приложения не дошёл, и смотреть надо на список серверов в блоке upstream и на их доступность.
  • В логе 504 и $upstream_response_time около 60 секунд: похоже на сработавший дефолтный таймаут, если его не меняли. Этот таймаут бывает и у соединения, и у чтения ответа, поэтому дальше надо различать: приложение молчало или до него не достучались по сети.
  • В логе nginx этого запроса нет совсем: одна из причин в том, что ответил кто-то раньше nginx, то есть внешний балансировщик, CDN или облачный прокси. Их таймауты и логи лежат в их собственной документации и консоли. Стоит заодно проверить, что открыт тот access-лог и тот server-блок.

Если приложений за nginx несколько, значения в этих переменных идут через запятую: адрес и статус на каждую попытку. По умолчанию (proxy_next_upstream error timeout) nginx на ошибке соединения или таймауте пробует следующий сервер, поэтому в строке лога может быть несколько адресов. Коды 500, 502, 503 и 504 от приложения считаются неудачной попыткой, только если их явно перечислили в этой директиве. Запрос передают дальше лишь пока клиенту ещё ничего не отправлено: оборвавшийся на середине ответ так не исправить. Запросы методов POST, LOCK и PATCH, уже ушедшие на сервер, без опции non_idempotent дальше не передаются.

Что проверить у себя

  • Проверить, что в формате access-лога nginx есть $upstream_addr, $upstream_status и $upstream_response_time. Без них разбор 502 и 504 превращается в догадки.
  • Выписать цепочку целиком: CDN, балансировщик, nginx, приложение. У каждого звена свой таймаут, и ответит тот, у кого он короче.
  • Сверить таймауты по цепочке: внешнее звено не должно ждать меньше внутреннего, иначе внутреннее ещё работает, а клиент уже получил 504.
  • Для плановых работ отдавать именно 503, желательно с Retry-After: так клиентам и мониторингу понятно, что это временно.
  • Завести отдельные алерты на 502, 503 и 504: у них разные причины, и в одну кучу «5xx» они склеиваются зря.
  • Не лечить 504 одним увеличением proxy_read_timeout, пока не выяснено, на чём приложение ждёт.

Разборы аварий и практику для админов публикуем в Telegram-канале «Полный Ops».

44