HTTP 500 означает, что сервер не смог обработать запрос; 502 — шлюз получил непригодный ответ от следующего сервера; 503 — сервис временно не готов обслуживать запрос; 504 — шлюз не дождался своевременного ответа. Сначала определите, какой слой отправил код: CDN, балансировщик, обратный прокси или приложение.

Одна и та же страница может отвечать разным кодом в зависимости от места сбоя. Если PHP выдал 500 и nginx переслал его, клиент увидит 500. Если nginx не смог подключиться к PHP-FPM, он может вернуть 502. Если upstream молчит слишком долго, ожидается 504. На практике конкретная настройка шлюза может преобразовывать ответы; поэтому код — начало маршрута, а не окончательный диагноз. Определения приведены в RFC 9110.
Найдите сервер, который сформировал ответ
Начните с заголовков, времени ответа и одного идентификатора запроса. Затем сопоставьте записи по времени на CDN, прокси и приложении. Если в access.log внутреннего сервиса запроса нет, ошибка возникла раньше в цепочке или запрос не достиг сервиса. Но отсутствие записи само по себе ещё не доказывает конкретный код: проверьте журнал и метрики предыдущего слоя. Схема обратного прокси и типичные точки отказа есть в разборе reverse proxy.
curl -sS -D - -o /dev/null -w 'code=%{http_code} total=%{time_total} ip=%{remote_ip}\n' https://example.com/
curl --fail-with-body -sS -D - -o /tmp/error-body.html https://example.com/
# Второй вызов вернёт exit 22 для HTTP 4xx/5xx; тело останется в файле.
Обычный curl возвращает exit 0, если HTTP-обмен прошёл, даже при коде 500. Для скрипта используйте --fail-with-body и отдельно сохраняйте статус. Сравните внешнюю проверку с внутренним URL приложения, если у вас есть доступ; не публикуйте приватный адрес в общем логе. В формате замера curl показано, как отделить ожидание первого байта от полной передачи.

Четыре кода — четыре разных первых проверки
Таблица ниже задаёт первый шаг, а не гарантированную причину. Согласно RFC 9110, 502 и 504 относятся к роли шлюза; приложение может ошибочно отдать эти коды само, но тогда логика протокола не помогает определить источник. Смотрите фактического отправителя ответа и конфигурацию маршрута.
| Код | Смысл | Что проверить первым | Типичный сигнал в журнале |
|---|---|---|---|
| 500 | Неожиданная ошибка обработки | Лог приложения по request ID, исключение, зависимость | Стек ошибки или ошибка обработчика |
| 502 | Непригодный ответ upstream для шлюза | Доступность сокета, reset, неверный протокол, состояние воркеров | connect() failed или upstream prematurely closed connection |
| 503 | Временная недоступность или перегрузка | Режим обслуживания, пул, лимиты, Retry-After | Явный отказ сервиса или отсутствие здоровых узлов |
| 504 | Шлюз не получил ответ вовремя | Таймаут чтения, долгий запрос, состояние upstream | upstream timed out |
Retry-After полезен для 503, когда сервис может назвать срок повторной попытки; это не обязательный заголовок для каждого ответа. Клиенту не следует бесконечно повторять запросы без backoff, особенно при перегрузке. Мониторинг должен разделять код и слой: проверка доступности снаружи видит пользовательский результат, а метрики API показывают, что происходит внутри.
Локальная двухзвенная проверка
Чтобы не приписывать коды реальному инциденту, мы воспроизвели четыре ситуации в локальном стенде: Python-приложение и отдельный HTTP-шлюз. Приложение вернуло 500; шлюз переслал его. Подключение к закрытому порту шлюз преобразовал в 502, режим обслуживания отдал 503 с Retry-After: 30, а ожидание медленного upstream дольше 0,2 с завершилось 504. Это поведение именно тестового шлюза, не обещание, что nginx на любой конфигурации выберет те же значения.
/backend-error HTTP/1.0 500 Internal Server Error
/bad-upstream HTTP/1.0 502 Bad Gateway
/maintenance HTTP/1.0 503 Service Unavailable
/timeout HTTP/1.0 504 Gateway Timeout
# curl exit code: 0 для всех четырёх без --fail-with-body
Стенд отделяет место ошибки от её отображения клиенту. 500 пришёл из приложения, остальные три кода сформировал шлюз. Если внешний CDN поверх шлюза перехватывает ответы, коды и заголовки могут измениться ещё раз. Поэтому при реальном сбое снимайте трассу по каждому переходу. Для регулярной проверки внешнего ответа можно использовать проверку сайта enterno.
Где искать после первого ответа
При 500 смотрите исключение приложения и недавно изменённый код, но не объявляйте базу причиной без трассы. При 502 проверьте, существует ли upstream-сокет, совпадают ли протокол и порт, есть ли свободные процессы. При 503 ищите объявленный maintenance, лимит нагрузки и число здоровых узлов. При 504 сравните таймауты всех прокси и длительность запроса внутри приложения; повышение таймаута без устранения причины может только продлить ожидание клиента. Общий справочник по классам ответа — HTTP-коды 100–599.
Как сделан материал
Значения кодов сверены с RFC 9110 13 ноября 2026 года. Локальный стенд method.py запускает приложение и шлюз на 127.0.0.1, сохраняет полный вывод curl, exit code и UTC в data.json. У этого стенда нет CDN, балансировщика и PHP-FPM, поэтому его вывод иллюстрирует слой формирования ответа, а не частоту инцидентов enterno или поведение всех веб-серверов. SerpAPI сохранён для RU и EN; Wordstat региона 225 показал отдельный спрос на сравнение 502 и 504, цифры не переносились в публичный текст.
Частые вопросы
Может ли приложение вернуть 502?
Технически да, но по семантике 502 описывает шлюз. Проверяйте, кто отправил ответ и было ли обращение к upstream.
Почему curl завершился успешно при HTTP 500?
Без --fail или --fail-with-body curl считает успешным сам HTTP-обмен. Код ответа проверяйте через %{http_code}.
Нужно ли всегда увеличивать proxy_read_timeout при 504?
Нет. Сначала измерьте длительность операции, состояние upstream и ожидания на промежуточных прокси. Затем меняйте конкретный лимит.
Что сообщает Retry-After при 503?
Сервер может подсказать срок новой попытки. Клиенту нужен ограниченный повтор с backoff; наличие заголовка не гарантирует, что сервис восстановится точно в этот момент.