# теория · шаг 3 из 7
Коды: кто их назначает
Коротко
Первая цифра кода ответа задаёт класс результата, остальные уточняют его. Код нужно читать вместе с заголовками и логом.
- 1xx - промежуточный ответ, 2xx - успешная обработка, 3xx - перенаправление или работа с сохранённой копией, 4xx - запрос нельзя выполнить, 5xx - ошибка сервера.
- У 204 и 304 нет тела. У редиректов 301, 302, 303, 307 и 308 есть Location.
- Коды 307 и 308 сохраняют метод запроса.
- Upstream-поля помогают установить, обращался ли nginx к приложению.
Если пять классов, пустое тело у 204 и достройка Location знакомы - листай до «Теперь сам».
Сначала ответь сам
В конфиге строка:
return 301 /new/;
Клиент пришёл на http://shop.local/old. Что окажется в заголовке Location?
Не /new/. nginx достроит абсолютный адрес, взяв имя из заголовка Host
запроса:
Location: http://shop.local/new/
Схема тоже подставится - та, по которой пришёл запрос. И вот тут заложена мина,
которую находят уже на проде: если nginx стоит за другим прокси и получает
Host: internal-app, то клиента отправят на http://internal-app/new/ - адрес,
которого в интернете не существует. Лечение будет в разделе про обратный прокси,
а причина - здесь.
На что это похоже
Справочное окно на вокзале. Ответов там ровно пять видов.
«Секунду, продолжаю» - вопрос принят, разговор не окончен. «Вот билет» - готово, держи результат. «Это в соседнем окне» - ответ есть, но в другом месте, и номер окна назовут. «Так спросить нельзя» - вопрос неверный: не тот бланк, нет паспорта, поезда с таким номером нет. И «у нас упала система» - вопрос был законный, сломалось у них.
Разница между четвёртым и пятым видом - это и есть разница между 4xx и 5xx, и она про то, кому идти чинить. А не про то, кто виноват: 404 бывает и из-за кривой ссылки на своём же сайте.
Механизм: пять классов и кто их назначает
Правило
Первая цифра говорит клиенту, что делать; кто назначил код - говорит лог.
Прочерк в $upstream_addr означает «к приложению не ходили»: ответил сам nginx
или его кеш. Адрес - «nginx пытался спросить приложение»; дошёл ли запрос,
говорит error_log. И 502 с 504 обычно назначает nginx, но не всегда: если
бэкенд сам прокси, их может прислать и он.
Разбор: чем отличаются ответы разных классов
Четыре ответа с одного стенда, только заголовки:
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 и то, сохраняется ли метод запроса.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий