PingMap beta
12 мин. чтения

502, 503, 504: что значат ошибки шлюза и как их чинить

502, 503 или 504 на сайте? Разбираем, чем отличаются ошибки шлюза, кто их отдаёт — ваш сервер или CDN — и как чинить: nginx, php-fpm, gunicorn, таймауты.

Пятисотая. 502, 503 или 504 — без разницы, в первую секунду реакция одна. Белый экран, три цифры, и ты не понимаешь: это мой сервер прилёг или кто-то перед ним дурит. Я эти три кода за карьеру повидал больше, чем все остальные пятисотые вместе. И каждый раз — один и тот же вопрос. Кто виноват: я или прокладка между мной и юзером?

Сразу скажу — выдохнуть можно. Это не «ой, всё сломалось». Это вполне конкретные жалобы прокси на того, кто стоит за ним. Понимаешь, кто и на что жалуется, — и не сидишь полночи в логах, гадая.

Что общего у всех трёх

Смотри, как это устроено. Перед твоим приложением почти всегда кто-то торчит — nginx, Apache, Traefik, балансер, Cloudflare, неважно. Он берёт запрос от юзера и идёт к апстриму: php-fpm, gunicorn, node, соседний сервис. И все три кода — про то, что в этой связке порвалось. Разница только в том, что апстрим ответил. Точнее — как именно не ответил:

КодЧто сказал апстримКороче
502 Bad Gatewayвыдал мусор, пустоту или вообще трупсломался
503 Service Unavailable«занят, отстань»отказался
504 Gateway Timeoutмолчит, как партизанзавис

Держишь в голове только это — уже половина диагностики. Сломался, отказался, завис. Вот, собственно, и вся теория. Теперь по каждому.

Если горит прямо сейчас:

  1. Глянь код снаружи — /tools/http-status-check/, чтобы понять, это у всех или только у тебя.
  2. Посмотри заголовок Server на /tools/headers/: если там cloudflare — ошибку может рисовать CDN, а не твой сервер (об этом ниже).
  3. 502 → бэкенд лёг. 504 → что-то тормозит. 503 → перегруз или деплой.
  4. И первым делом — error.log у nginx. Он почти всегда стучит на виновника прямым текстом.

502 Bad Gateway — апстрим сломался

  1. Прокси сходил за ответом и принёс оттуда дрянь: коннект отбили, оборвали на полуслове, бэкенд умер. Сам nginx живёхонький — он-то старался, не вышло. Первым делом — в лог:
tail -f /var/log/nginx/error.log

Что ты там увидишь и что это значит:

  • connect() failed (111: Connection refused) — апстрим не слушает порт или сокет. php-fpm или gunicorn просто не поднялся: упал, не стартанул после деплоя, перезапускается.
  • upstream prematurely closed connection — воркер схватил запрос и сдох на середине. Классика OOM: killer прихлопнул процесс под нагрузкой.
  • no live upstreams — все бэкенды в блоке помечены как dead, балансеру некуда идти.
  • upstream sent too big header — апстрим-то ответил, но заголовки распухли и не влезли в буфер nginx. Жирная кука, портянка-JWT, гора заголовков — частые виновники. Это не про код, а про буферы (про них ниже).

Если апстрим жив и слушает, а 502 всё равно держится — почти всегда несовпадение адреса:

systemctl status php8.2-fpm    # или gunicorn, uwsgi, твой сервис
ss -ltnp | grep -E '9000|8000'  # слушает ли он вообще свой порт/сокет

nginx ходит на 127.0.0.1:9000, а php-fpm втихую переехал на unix-сокет (или наоборот). Сверь fastcgi_pass / proxy_pass с тем, что реально слушает апстрим.

Отдельная засада, на которой я сам не раз обжигался, — 502 только под нагрузкой. Днём тишь да гладь, в час пик — сыпет. Почти всегда воркеры: их мало, под нагрузкой они либо заняты все до единого, либо начинают мереть по памяти. В логе php-fpm тогда будет server reached pm.max_children setting (N), consider raising it. Лечить вслепую не вздумай. Сначала прикинь, сколько ест один воркер, и только потом поднимай pm.max_children под реальную оперативку. Иначе схлопочешь OOM и те же 502, только теперь злее.

503 Service Unavailable — апстрим отказался

503 — отдельная история. Тут вообще ничего не ломалось. Сервер на месте, в трезвом уме, и сознательно говорит: не сейчас. И — внимание — чаще всего эту фразу произносит не само приложение, а прокси или балансер перед ним. По убыванию частоты:

  • Деплой. Кто-то катит релиз, фреймворк (Laravel php artisan down и компания) сам вешает 503 на время. Ждали выкатку — ну вот она, выдыхай.
  • Rate limit. nginx с limit_req/limit_conn по дефолту отвечает именно 503, когда клиент превысил лимит (код настраивается, многие меняют на 429). Если 503 ловит конкретный бот или один IP — тебе сюда.
  • Ограничитель на перегрузе. А вот тонкий момент, на котором путаются все. Забитый пул воркеров чистый 503 не даёт. Когда у php-fpm кончились руки (max_children), запросы тупо встают в очередь и потом выливаются в 502 или 504 — это разбирали выше. 503 на перегрузе отдаёт кто-то, кто специально поставлен ограничивать: limit_conn у nginx, лимит соединений на балансере или сам апплейн.
  • Балансер без живых бэкендов. Если впереди отдельный балансер (HAProxy, ALB, Traefik, ingress в Kubernetes) и все бэкенды не прошли health-check — он отдаёт 503. А вот nginx в похожей ситуации (все апстримы в upstream{} мертвы) — наоборот, 502. Не перепутай: беда одна, коды разные.

Полезный сигнал — заголовок Retry-After. Шлёт его сервер — значит, 503 плановый (деплой, rate limit), а не паника. Глянуть заголовки удобно на /tools/headers/, не лазая по SSH.

Лечение — по причине. Забыли выключить maintenance — выключаешь. Rate limit косит живых людей, а не бота — ослабляешь или целишь точнее. Балансер выкинул бэкенды — чинишь health-check. А если 503 сознательно рисует сам апплейн — лезешь в его код или конфиг, где он это делает.

504 Gateway Timeout — апстрим завис

  1. Прокси ждал-ждал ответа — и плюнул. Апстрим взял запрос и завис дольше отведённого таймаута (у nginx по дефолту proxy_read_timeout и fastcgi_read_timeout — 60 секунд). В логе это выглядит так:
upstream timed out (110: Connection timed out) while reading response from upstream

И вот тут главное не вестись на соблазн. 504 — это не про nginx, это про то, что за ним тормозит. Самое заманчивое — задрать таймаут до пяти минут и забыть. Не надо так, по крайней мере не первым шагом. Это не лечение, это анестезия: вместо 504 за минуту юзер будет залипать в белый экран все пять, что ещё хуже. Сначала ищи, что тормозит:

  • Медленный запрос в базу. Чаще всего он. Включи slow query log, найди запрос, который встал после последнего деплоя — привет, потерянный индекс или N+1.
  • Внешний вызов без таймаута. Дёрнули чужой API, тот повис, и твой воркер повис вместе с ним. У всех внешних вызовов должен быть свой таймаут, короче, чем у nginx.
  • Тяжёлая операция прямо в запросе. Генерёжка отчёта, ресайз картинки, выгрузка — такое выносят в фон, а не держат в HTTP.

И только когда понял, что операция честно долгая (большой экспорт, например) — тогда осознанно поднимаешь таймаут именно для этого маршрута, а не глобально на весь сервер.

Ручки nginx, которыми всё это крутится

Почти все три кода в nginx сидят на нескольких директивах. Держу их в одном месте, чтобы каждый раз не вспоминать:

location / {
    proxy_pass http://backend;

    proxy_connect_timeout 5s;    # не дождались установки соединения → 504
    proxy_read_timeout    60s;   # дефолт; не дождались ответа → 504
    proxy_buffer_size     16k;   # маленький буфер заголовков → 502 "too big header"
    proxy_buffers         8 16k;
}

Для связки с php-fpm всё то же самое, только с приставкой fastcgi_: fastcgi_read_timeout, fastcgi_buffer_size, fastcgi_buffers. И ещё пара мест, куда стоит заглянуть, когда 502/504 прилетают на ровном месте:

  • keepalive к апстриму. Включил в upstream{} keepalive, а сам апстрим рубит простаивающие соединения быстрее, чем nginx успевает их переиспользовать, — и вот тебе плавающий 502 upstream prematurely closed connection вообще без всякого OOM. Лечится согласованием таймаутов или proxy_http_version 1.1 плюс proxy_set_header Connection "".
  • proxy_next_upstream. Решает, ретраить ли запрос на другой бэкенд при ошибке. По дефолту часть случаев ретраит — иногда это спасает, иногда наоборот: маскирует проблему или дублирует тяжёлый запрос.

И главное — помни, что все эти директивы про то, как nginx ждёт и принимает ответ. Причину (мёртвый воркер, медленный запрос, распухшие заголовки) они не чинят — только меняют, каким кодом nginx в итоге сдастся. Крутить их вслепую — это ровно тот случай «анестезия вместо лечения», про который выше.

Кто вообще рисует ошибку — ты или CDN?

С этого я начинаю всегда. По молодости я честно убил пару ночей на логи nginx, которых в той ошибке и быть не могло: перед сайтом стоял Cloudflare, и пятисотую рисовал он, а мой сервер в это время преспокойно жил. Просто запрос до него не доезжал.

Различить — секундное дело, по заголовкам ответа (/tools/headers/ или curl -sI):

  • Server: cloudflare и заголовок cf-ray — значит, ты за облаком. Сам по себе cf-ray есть и на здоровых 200, так что это маркер «CDN впереди», а не ошибки. На странице ошибки Cloudflare ещё рисует схему «Браузер — Cloudflare — Host» и подсвечивает, чей узел красный.
  • И у Cloudflare свои коды, которых в стандарте HTTP нет: 521 — origin отказал в коннекте (процесс веб-сервера лёг или фаервол режет IP Cloudflare), 522 — не дождался TCP-хендшейка (origin не ответил на соединение), 523 — вообще не нашёл, куда ехать (битый IP/DNS), 524 — соединился, но твоё приложение думало над ответом дольше ста секунд. Вот 524 — это и есть честный 504, только глазами облака. Увидел 52x — копай связь CDN ↔ origin и сам origin.

Поэтому код полезнее смотреть снаружи, а не из своего браузера: /tools/http-status-check/ покажет ровно то, что отдаётся клиенту, вместе с заголовками — и сразу видно, чей это пятисотый.

История с проды

Пятница, вечер — а когда ещё. После выката сайт начал плеваться 502. Не постоянно, раз в пару минут, на рандомных запросах — самое мерзкое, такое и поймать-то тяжело. В логах nginx: upstream prematurely closed connection. То есть воркер берёт запрос и дохнет.

Первая мысль, понятно, — выкатили баг, код падает. Лезу в лог приложения. Чисто. Ни единого трейсбека. Стоп, думаю: падал бы код — был бы стектрейс. А его нет.

Глянул dmesg:

Out of memory: Killed process 21438 (gunicorn)

Вот оно. Не код падал — его расстреливали из ядра. В релизе кто-то поднял размер кэша в памяти на воркер, воркеров было восемь, и под нагрузкой они дружно вылезли за оперативку. Ядро начало отстреливать самого жирного — gunicorn-воркер — ровно посреди запроса. nginx видел обрубок и честно рисовал 502.

Починили в две строчки: вернули кэш на место, воркеров временно срезали до шести, пока не добавили памяти. 502 пропали сразу. А я ведь мог до утра искать баг, которого нет. Вывод, который я с тех пор повторяю как мантру: пятисотая почти никогда не живёт там, где написан её код. nginx — гонец, не стреляй в него.

Чтобы не узнавать о 5xx от клиентов

А знаешь, что в этих трёх самое подлое? Они мигают. Выскочило на пике, отпустило, через десять минут снова. Руками такое не словишь: откроешь сайт — а он как назло отдаёт 200, будто и не было ничего. Поймать может только тот, кто долбит сайт без остановки и фиксирует код, даже мелькнувший на минуту где-то в три ночи.

В PingMap я ставлю проверку HTTP-статуса каждые 30 секунд из нескольких регионов: прилетела 5xx — через пару секунд алёрт в Telegram, с самим кодом. А по коду уже на ходу понятно, куда бежать: 502 — бэкенд, 504 — что-то тормозит, 503 — перегруз. Это мониторинг доступности, настраивается за пару минут, на бесплатном тарифе на один сайт хватает с головой.

И отдельно: если хочешь разово прогнать сайт прямо сейчас и из разных точек — быстрая проверка доступности покажет код, время ответа и заголовки без всякой регистрации.

FAQ

Чем 502 отличается от 504, если оба про шлюз? 502 — апстрим ответил плохо (пусто, битый ответ, оборвал соединение) или вовсе не слушает. 504 — апстрим не ответил вовремя, соединение было, но он молчал дольше таймаута. Грубо: 502 — бэкенд сломан или мёртв, 504 — бэкенд жив, но тормозит. И в логах nginx они разные: connection refused/prematurely closed против upstream timed out.

Сайт отдаёт 503, а я ничего не выкатывал. Что это? Раз деплоя не было — скорее всего перегруз (все воркеры заняты) или rate limit. Проверь заголовок Retry-After и лог апстрима на max_children или кончившиеся воркеры. Ещё вариант — балансер потерял все живые бэкенды по health-check. Реже — кто-то забыл выключить maintenance.

Помогает ли просто увеличить таймаут при 504? Иногда прячет симптом, но почти никогда не лечит. Если тормозит медленный запрос или зависший внешний вызов, больший таймаут просто заставит юзера дольше пялиться в белый экран. Сначала найди, что тормозит (slow query log, таймауты на внешние вызовы), и поднимай таймаут осознанно — только под реально длинные операции.

Как понять, 5xx рисует мой сервер или Cloudflare? По заголовкам ответа. Server: cloudflare — ответ собрал CDN. Коды 521524 — нестандартные, чисто cloudflareвские. Только не читай их как «проблема всегда между CDN и origin»: 521 (origin отказал) и 524 (origin не ответил за ~100с) тычут как раз в сам origin — лёг процесс или тормозит приложение; а 522/523 — это уже про связь и маршрут. А если Server: nginx и при этом есть cf-ray — значит 5xx отдал твой origin, а Cloudflare просто пробросил.

Влияет ли 5xx на SEO? Разовые и недолгие — почти нет: поисковик придёт позже и переиндексирует. Но если бот раз за разом натыкается на 5xx, он реже краулит и может временно выкинуть страницы из выдачи. 503 с Retry-After поисковики понимают правильно («зайди позже»), хотя Google и относится к самому значению как к подсказке, а не строгой команде. И держи в голове: 5xx на robots.txt бьёт куда больнее, чем на обычной странице, — Google на это нередко притормаживает обход всего хоста. Так что важен не разовый инцидент, а то, чтобы он не висел неделями незамеченным.

Сначала ловлю 504, а коннекта вообще нет — это одно и то же? Нет, и это полезно различать. 504 ты получаешь от прокси — значит, он до апстрима достучался (ну или хотя бы пытался) и не дождался ответа. А если TCP-соединение к самому сайту вообще не встаёт (висит или refused), ты и кода не увидишь — это уровень ниже, ближе к сети. Тогда диагностика другая — проверка TCP-соединения. А если сайт не открывается совсем и непонятно, с чего начать, — есть общий разбор по слоям.

В сухом остатке

Три кода — три диагноза: сломался (502), отказался (503), завис (504). Таблица с деталями — в начале, держать всё это в голове не надо.

Что реально стоит унести: первым делом глянь по заголовкам, чей это пятисотый — твой origin или CDN перед ним. Это снимает половину ложных следов и экономит те самые «полночи в чужих логах». А дальше код сам подскажет слой: 502 — бэкенд, 503 — ограничитель или деплой, 504 — что-то тормозит.

И чтобы не ловить плавающие 5xx руками — поставь мониторинг HTTP-статуса: он замечает код за секунды, даже если тот мелькнул на минуту в три ночи.

Настройте мониторинг за 2 минуты

Добавьте URL, выберите канал уведомлений — и получайте сигнал о реальных проблемах.