# теория · шаг 1 из 5
4xx: чей это на самом деле код
Коротко
Код 4xx говорит категорию, а причину - строка в error_log. И главное: часть
этих кодов nginx выдаёт сам, не спросив приложение, поэтому в логах бэкенда
такого запроса нет вовсе.
- 403 бывает трёх природ, и у каждой своя строка: нет прав, каталог без
index-файла, сработавший
deny. - 413 отдаётся до бэкенда:
client_max_body_sizeпо умолчанию 1 МБ, в логеupstream=-, время 0,001 с. - 404 надо читать по пути в кавычках, а не по URI из запроса.
- 499 - код самого nginx: клиент ушёл, не дождавшись.
- Объяснения 400 и 499 идут на уровне
infoи ни приwarn, ни приerrorне пишутся.
Если три природы 403 и происхождение 413 и 499 знакомы - листай до «Теперь сам».
Сначала ответь сам
Пользователь не может загрузить видео: маленькие картинки уходят, файл на 26 мегабайт обрывается ошибкой. Разработчик смотрит логи приложения и говорит: «У меня такого запроса нет. Это не мы».
Он прав. Строка в логе nginx:
127.0.0.1 - - [10/Sep/2026:19:59:03 +0000] "POST /api/upload HTTP/1.1" 413 183 "-" "curl/8.22.0" host=localhost rt=0.000 upstream=- us=- urt=- uct=- uht=- rid=db9d77ba17a687ff9a098c91b8ce34a0
upstream=- означает, что запрос до бэкенда не дошёл ни на миллиметр. nginx
прочитал заголовок Content-Length, сравнил с client_max_body_size и ответил
за одну миллисекунду, тела даже не читая:
2026/09/10 19:59:03 [error] 8#8: *12 client intended to send too large body: 26000000 bytes, client: 127.0.0.1, server: shop.local, request: "POST /api/upload HTTP/1.1", host: "localhost"
8#8 - номер процесса и потока, *12 - номер соединения: по нему в логе
находятся все строки одного и того же клиента.
Разговор «это не мы» заканчивается на слове upstream=-.
На что это похоже
У здания есть турникет, а внутри - отдел, куда человек шёл. Часть посетителей разворачивают на турникете: нет пропуска, слишком большая сумка, странно заполненный бланк. В отделе про них не знают ничего - там не было ни звонка, ни записи.
Идти в отдел с вопросом «почему его не приняли» бессмысленно. Смотреть надо журнал охраны, и там записано, на чём именно развернули.
4xx делятся ровно так: часть кодов - решение турникета, часть - решение отдела.
Механизм: где именно запрос умирает
Правило
Сначала посмотри, есть ли в строке upstream. Прочерк означает, что решение
принял nginx, и разбираться надо в его конфиге. Адрес означает, что запрос дошёл,
и код мог прийти от приложения.
Разбор: 403 в трёх видах
Три запроса на стенде, три одинаковых кода, три разные причины:
grep -v выбрасывает строки со словом: записи про закрытые keepalive-соединения
здесь только шум.
[error] open() "/www/locked/a.txt" failed (13: Permission denied)
[error] directory index of "/www/dir_no_index/" is forbidden
[error] access forbidden by rule
| Строка | Причина | Чем чинить |
|---|---|---|
failed (13: Permission denied) |
воркеру не хватает прав на файл или на каталог пути | владелец и права, x на каждый каталог пути |
directory index of "..." is forbidden |
запрос на каталог, index-файла нет | index, try_files или autoindex on |
access forbidden by rule |
сработал deny |
правила allow/deny в location, вроде deny all; |
Различие принципиальное: первое чинится в файловой системе, второе - в конфиге, третье вообще не чинится - кто-то настроил запрет специально, и прежде чем его снимать, стоит выяснить зачем.
404 читается тем же приёмом - по пути:
[error] open() "/www/net/takogo.css" failed (2: No such file or directory)
Читай путь в кавычках, а не URI из запроса: он показывает, во что URI
превратился после root и alias. Если файлы лежат в другом каталоге, чинить
надо конфиг, а не выкладку. Номера в скобках - коды системного вызова: 2 - нет
файла, 13 - нет прав.
Разбор: где наивное правило подводит
Половины строк при обычном уровне нет. Объяснения 400 и 499 nginx пишет на
уровне info, а в конфигах стоит уровень выше: в пакете Debian он не назван,
то есть действует error, а инструкции обычно советуют warn. Проверено на стенде: при
warn за секунду с кодом 400 в error_log не появляется ничего. Опустишь
уровень - увидишь:
[info] client sent too long header line: "X-Big: aaaa..." while reading client request headers
Это, кстати, самая частая причина 400 в живом проекте: у приложения разрослись
куки. Лечится large_client_header_buffers 4 16k; (умолчание - 4 8k: четыре
буфера по 8 КБ), но сначала стоит спросить, зачем
приложению заголовки на десятки килобайт.
400 бывает и там, где всё «по стандарту». Запрос без заголовка Host по
HTTP/1.1 отвергается сразу, а тот же запрос по HTTP/1.0 отрабатывает нормально -
в старой версии протокола Host необязателен. Проверить можно так:
curl -H 'Host:' localhost/ убирает заголовок, а -0 переключает curl на
HTTP/1.0. Проверено: 400 против 200 на одном
и том же конфиге. Так что «у меня скриптом работает, а у них нет» иногда
объясняется одной строкой в самодельном клиенте.
499 - это не поломка nginx. Кода нет в стандарте, его придумал сам nginx, и означает он «клиент закрыл соединение раньше, чем пришёл ответ». В строке получается любопытная картина:
"GET /api/slow HTTP/1.1" 499 0 rt=1.001 upstream=172.24.0.2:8000 us=- urt=1.001 uht=-
us=- и uht=- - бэкенд не успел отдать даже заголовки; urt=1.001 - столько
nginx прождал до обрыва. Ровно секунда, потому что у клиента стоял таймаут в
секунду, а бэкенд думал шесть.
За 499 обычно стоит одно из трёх: бэкенд отвечает дольше, чем ждёт клиент; таймаут стоит на CDN или балансировщике перед nginx; кто-то сканирует сайт и рвёт соединения, не читая ответы. Первое - самое частое, и лечится оно скоростью бэкенда, а не настройками nginx.
Что ломается без этого
Команда неделю ищет ошибку в приложении, которого запрос не касался. Классика -
413: пользователи жалуются, разработчик разводит руками, потому что в его логах
чисто, а нужное слово client_max_body_size не произносит никто.
Второе - 403 «вообще». Без разбора на три природы человек начинает наугад
раздавать права chmod 777, хотя строка в логе говорила access forbidden by
rule: права ни при чём, кто-то поставил запрет намеренно.
Зачем это в работе
Список того, что кто-то ищет, а сервер не отдаёт:
awk '$9 == 404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
Условие перед фигурными скобками отбирает строки, где девятое поле - код -
равно 404, и печатает у них седьмое - адрес. head оставляет первые десять.
Он всегда интереснее, чем кажется: в нём и опечатки в шаблонах, и забытые при
переезде файлы, и чужие сканеры, ищущие /.env.
Лимит на тело поднимают точечно, а не на весь сервер:
location /api/upload {
client_max_body_size 100m;
proxy_pass http://127.0.0.1:8080;
}
Сто мегабайт на весь сайт означают, что любой адрес вправе занять столько памяти и диска на каждый запрос. В том location, где действительно принимают файлы, лимит поднимают; везде ещё оставляют маленьким.
Вежливый клиент умеет спросить разрешение до отправки: он шлёт заголовок
Expect: 100-continue и ждёт. nginx отвечает сам, не спрашивая бэкенд, и ответ
зависит от лимита. Два обмена на стенде с client_max_body_size 1m: сначала
тело в 5 байт, потом в 26 МБ (-v печатает заголовки обмена, > - то, что
ушло, < - то, что пришло):
curl -sv -H 'Expect: 100-continue' --data-binary @tiny localhost/api/upload 2>&1 | grep -E '^< HTTP|^> Expect'
> Expect: 100-continue
< HTTP/1.1 100 Continue
< HTTP/1.1 404 Not Found
curl -sv -H 'Expect: 100-continue' --data-binary @big localhost/api/upload 2>&1 | grep -E '^< HTTP|^> Expect'
> Expect: 100-continue
< HTTP/1.1 413 Request Entity Too Large
В первом обмене nginx разрешил отправку, и дальше ответил бэкенд - здесь 404,
такого адреса у него нет. Во втором 26 мегабайт по сети даже не поехали: отказ
пришёл до тела.
Поэтому curl при загрузке крупных файлов шлёт этот заголовок сам, а вот
самодельные клиенты обычно нет - и гоняют файл впустую, чтобы получить тот же
413 в конце.
Теперь сам
В access_log пачка строк: код 499, rt около 30 секунд, us=-, все с одного
адреса, все на /api/report. error_log на уровне warn молчит. Что происходит и что
делать?
Клиент ждёт тридцать секунд и уходит, не дождавшись, - раз за разом. Виноват не
nginx: us=- значит, что бэкенд за тридцать секунд не отдал даже заголовков,
то есть действительно не успевает. Чинить надо отчёт на бэкенде либо делать его
асинхронным. Молчание error_log объясняется уровнем: строку про ушедшего
клиента пишут на info. А один адрес на все строки - повод посмотреть, не
повторяет ли клиент запрос автоматически после каждого таймаута.
Главное
Прочерк в upstream означает, что код придумал nginx, а не приложение: 413 (по
умолчанию лимит 1 МБ) отдаётся до чтения тела, 403 и 404 - при поиске файла и
проверке доступа. У 403 три природы, и различает их строка: (13: Permission
denied), directory index ... is forbidden, access forbidden by rule. В 404
читай путь в кавычках - это то, во что URI превратился. 499 - код самого nginx о
клиенте, который не дождался. Объяснения 400 и 499 пишутся на уровне info и при
warn не появляются вовсе.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий