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

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

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

Коротко

Код ответа - три цифры, и первая из них важнее двух остальных: она говорит, что делать дальше. А ещё код почти всегда назначает кто-то один - либо nginx, либо приложение, - и по логу видно, кто именно.

  • Пять классов: 1xx - «продолжай», 2xx - «готово», 3xx - «иди туда», 4xx - «ты спросил неправильно», 5xx - «у меня сломалось».
  • У 204 и 304 тела нет по определению, у 3xx обязателен заголовок Location.
  • Относительный адрес в return 301 /path nginx достраивает до абсолютного из заголовка Host - за прокси это выстреливает.
  • 1xx - промежуточные: в access_log они не попадают.
  • Код может быть любым числом: nginx отдаст 418 без названия, потому что названия не знает.

Если пять классов, пустое тело у 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», адрес - «спросили приложение».

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

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

↳  вывод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 тела нет по определению. Не «пустое тело», а его отсутствие как часть правила: клиент, получив такой код, за телом не идёт. Поэтому в 304 нет и Content-Length - иначе браузер стал бы ждать данные, которых не будет.

У редиректа тело есть. Сто шестьдесят девять байт - это готовая 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, клиент получил ошибку приложения; если прочерк - приложение не ответило, и разбираться надо с его падением.

Главное

Пять классов: 1xx - промежуточный ответ (в access_log его нет), 2xx - готово, 3xx - «иди туда» (плюс 304 «твоя копия годится»), 4xx - запрос выполнить нельзя, 5xx - сломалось на сервере. У 204 и 304 тела нет по определению, у редиректа обязателен Location, и относительный адрес nginx достраивает из Host. 301 и 308 постоянные - браузер их запоминает; 307 и 308 сохраняют методМетодПервое слово в строке запроса: GET - дай, POST - прими, HEAD - ответь как на GET, но без тела, DELETE - удали. Сервер вправе обрабатывать разные методы по-разному, а nginx умеет их различать директивой limit_except.. Кто назначил код, говорит не код, а $upstream_addr в логе.

Комментарии

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

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

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