1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

# теория · шаг 3 из 7

Коды: кто их назначает

Коротко

Первая цифра кода ответа задаёт класс результата, остальные уточняют его. Код нужно читать вместе с заголовками и логом.

  • 1xx - промежуточный ответ, 2xx - успешная обработка, 3xx - перенаправление или работа с сохранённой копией, 4xx - запрос нельзя выполнить, 5xx - ошибка сервера.
  • У 204 и 304 нет тела. У редиректов 301, 302, 303, 307 и 308 есть Location.
  • Коды 307 и 308 сохраняют метод запроса.
  • Upstream-поля помогают установить, обращался ли nginx к приложению.

Если пять классов, пустое тело у 204 и достройка Location знакомы - листай до «Теперь сам».

Сначала ответь сам

В конфиге строка:

/etc/nginx/conf.d/shop.local.confhttp › server › location /old
return 301 /new/;

Клиент пришёл на http://shop.local/old. Что окажется в заголовке Location?

Не /new/. nginx достроит абсолютный адрес, взяв имя из заголовка Host запроса:

↳  выводcurl -sI -H 'Host: shop.local' http://lab.local/old
Location: http://shop.local/new/

Схема тоже подставится - та, по которой пришёл запрос. И вот тут заложена мина, которую находят уже на проде: если nginx стоит за другим прокси и получает Host: internal-app, то клиента отправят на http://internal-app/new/ - адрес, которого в интернете не существует. Лечение будет в разделе про обратный прокси, а причина - здесь.

На что это похоже

Справочное окно на вокзале. Ответов там ровно пять видов.

«Секунду, продолжаю» - вопрос принят, разговор не окончен. «Вот билет» - готово, держи результат. «Это в соседнем окне» - ответ есть, но в другом месте, и номер окна назовут. «Так спросить нельзя» - вопрос неверный: не тот бланк, нет паспорта, поезда с таким номером нет. И «у нас упала система» - вопрос был законный, сломалось у них.

Разница между четвёртым и пятым видом - это и есть разница между 4xx и 5xx, и она про то, кому идти чинить. А не про то, кто виноват: 404 бывает и из-за кривой ссылки на своём же сайте.

Механизм: пять классов и кто их назначает

1xx продолжай: ответ ещё не окончательный почти всегда nginx, в access_log не попадает 2xx готово: 200, 204 без тела, 206 кусок файла файл - nginx, данные - приложение 3xx иди туда: 301, 302, 307, 308 и отдельно 304 чаще nginx: return, rewrite, кеш-заголовки 4xx спросил неправильно: 400, 403, 404, 413, 429 оба; кто именно - видно по upstream в логе 5xx сломалось у сервера: 500, 502, 503, 504 502 и 504 - обычно nginx, 500 - чаще приложение Вторые две цифры уточняют, но решение по ответу принимают по первой.
Классов пять, а вопросов при разборе два: что делать клиенту и кто назначил код. Второй вопрос решается по строке лога, а не по самому коду.

Правило

Первая цифра говорит клиенту, что делать; кто назначил код - говорит лог. Прочерк в $upstream_addr означает «к приложению не ходили»: ответил сам nginx или его кеш. Адрес - «nginx пытался спросить приложение»; дошёл ли запрос, говорит error_log. И 502 с 504 обычно назначает nginx, но не всегда: если бэкенд сам прокси, их может прислать и он.

Разбор: чем отличаются ответы разных классов

Четыре ответа с одного стенда, только заголовки:

↳  выводcurl -sD- -o /dev/null по четырём адресам
HTTP/1.1 204 No Content
(ни Content-Length, ни Content-Type, ни байта тела)

HTTP/1.1 301 Moved Permanently
Content-Type: text/html
Content-Length: 169
Location: http://localhost/f.txt

HTTP/1.1 304 Not Modified
Last-Modified: Thu, 03 Sep 2026 15:36:38 GMT
ETag: "6a999406-12c"
(тела нет, Content-Length тоже нет)

HTTP/1.1 418
Content-Length: 0

Что тут стоит заметить.

У 204 и 304 тела нет по определению. Не «пустое тело», а его отсутствие как часть правила: клиент, получив такой код, за телом не идёт. С Content-Length у них разные правила. В 204 его быть не должно вовсе. В 304 он разрешён и означает размер файла, который пришёл бы с кодом 200, - ждать тела браузер не станет, он знает о его отсутствии по коду. nginx в своём 304 этот заголовок просто не шлёт.

У редиректа тело есть. Сто шестьдесят девять байт - это готовая HTML-страница «301 Moved Permanently» на случай, если клиент не умеет ходить по Location сам. Браузеры её не показывают, а вот в curl без -L она видна.

Код 418 отдался без названия. В статус-строке пусто после числа: nginx знает названия только для кодов, которые умеет отдавать сам. Это хорошее напоминание, что название в статус-строке ни на что не влияет - клиент читает три цифры.

Отдельно стоит 444 - код, которого в стандарте нет. nginx по нему закрывает соединение, ничего не отвечая, и клиент видит не ошибку HTTP, а обрыв: curl завершается с кодом 52 «пустой ответ от сервера».

Разбор: где наивное правило подводит

«4xx - вина клиента» неверно. Битая ссылка в собственном шаблоне даёт 404, и клиент тут ни при чём. Правильная формулировка - «запрос в таком виде выполнить нельзя», а кто его таким сделал, код не говорит.

«3xx - это всегда редирект» тоже неверно. В классе живёт 304 Not Modified, и он не про «иди туда», а про «то, что у тебя, ещё годится». Разница видна по наличию Location: у редиректов он обязателен, у 304 его нет.

Постоянный редирект браузер запоминает. 301 и 308 кешируются, и человек, однажды получивший такой ответ, пойдёт по новому адресу, даже если ты уже вернул конфиг обратно. Чинится очисткой кеша у каждого посетителя, то есть практически никак. Поэтому включать 301 стоит после проверки на 302, а разница между парами такая:

Код Постоянный Метод сохраняется
301 да нет: POST может стать GET
302 нет нет: то же самое
307 нет да
308 да да

Пары 307/308 появились как раз потому, что старые клиенты превращали POST в GET при переходе, и форма отправлялась второй раз впустую.

1xx в логе не видно. Промежуточный ответ - не окончательный, и access_log пишет одну строку на запрос, с итоговым кодом. Проверено: обмен, в котором nginx отдал 100 Continue, а потом 200 OK, дал в логе одну строку с кодом 200.

Что ломается без этого

Самая дорогая ошибка класса - поспешный 301. Его ставят «на минутку», проверить переезд, а потом полгода получают письма от людей, у которых сайт открывается не там. Второе по частоте - редирект на внутреннее имя из-за Host: работает у всех, кроме тех, кто ходит через внешний балансировщик.

Зачем это в работе

Посмотреть цепочку переходов целиком, не гадая:

$  команда
curl -sIL -o /dev/null -w '%{http_code} %{url_effective}\n' https://shop.local/old

С флагом -L curl идёт по Location до конца, и в выводе видно каждый шаг. Цепочка длиннее двух звеньев - уже повод разобраться: каждый лишний переход это целый круг по сетиКруг по сетиВремя, за которое сигнал доходит до сервера и возвращается. По городу это единицы миллисекунд, до другого континента - под двести. Каждый шаг протокола (установка соединения, шифрование, сам запрос) стоит минимум одного круга., а на мобильной связи круг стоит сотню миллисекунд.

Теперь сам

Приложение отдаёт 500, а в логе nginx строка 502 с адресом бэкенда в $upstream_addr. Кто из них прав и что случилось?

Оба правы, и это разные события. 500 - код, который бэкенд успел отдать сам: он жив, у него внутренняя ошибка. 502 nginx ставит, когда ответа не было вовсе, - значит после той ошибки процесс упал или соединение оборвалось. Смотреть надо $upstream_status в той же строке: если там 500, клиент получил ошибку приложения; если 502 - nginx ответа не получил, и разбираться надо с падением приложения. Прочерка там не будет: прочерк значит, что к бэкенду не ходили вовсе.

Главное

Код ответа описывает результат обработки, но сам по себе не называет виновника ошибки. Чтобы отличить ответ приложения от ответа nginx, сопоставь его с $upstream_addr и $upstream_status. При проверке редиректа смотри также Location и то, сохраняется ли метод запроса.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.