# теория · шаг 1 из 5
Кандидаты и назначение
Коротко
Почти все странности отдачи статики растут из одного места: последний аргумент
try_files устроен не так, как все остальные.
- Все аргументы, кроме последнего, - кандидаты. Проверяются на диске по
правилам
root/alias, отдаётся первый найденный. - Последний - назначение. На существование не проверяется вообще. Форм
три:
=код,@имя, URI. - Назначение-URI запускает обработку заново: новый выбор location, новый круг директив.
- Если новый URI снова попадает в тот же
try_files- цикл и 500. Попадёт в другой location - обычный 404. Одна и та же опечатка, два разных симптома. - Каждый кандидат стоит обращения к диску, поэтому список держат коротким.
Если различие «кандидат / назначение» уже очевидно - листай до «Теперь сам».
Сначала ответь сам
root /var/www/shop;
location / {
try_files $uri $uri/ =404;
}
На диске есть about.html и каталог docs/ с index.html внутри. Три
запроса: /about.html, /docs/, /report.
Первые два очевидны. Вопрос про третий: почему nginx вернул 404, а не стал
искать файл с именем 404?
На что это похоже
try_files - это связка ключей и запасной план.
Сторож пробует ключи по очереди: первый подошёл - дверь открыта, остальные не трогаем. А в конце связки висит не ключ, а записка: «если ни один не подошёл - позвони по этому номеру». Записку никто не пробует вставить в замок: она устроена иначе и делает другую работу.
Ошибка почти всех - считать записку последним ключом. Отсюда и вопросы вида
«почему nginx не ищет файл 404»: потому что это не кандидат, это план на
случай неудачи.
Механизм: кандидаты и одно назначение
Три формы назначения:
try_files $uri $uri/ =404; # вернуть код
try_files $uri $uri/ /index.html; # внутреннее перенаправление на URI
try_files $uri @backend; # передать в именованный location
Кандидат со слешем на конце ($uri/) - это проверка каталога, а не файла.
Каталог существует - запрос обрабатывается как обычный запрос на каталог:
включается index, отдаётся index.html изнутри. Порядок кандидатов и есть
порядок приоритета.
Правило
Все аргументы, кроме последнего, проверяются на диске; последний - назначение и не проверяется. Назначение-URI начинает обработку заново, с нового выбора location.
Разбор: одна опечатка, два разных симптома
Внутреннее перенаправление - это новый круг: заново выбирается location, заново
применяются директивы. Метод при этом остаётся прежним: POST, который
try_files отправил на статический файл, получает 405 - проверено. Наружу
ничего не уходит, браузер ничего не замечает.
Отсюда классическая авария - и вот тут интересное. Проверено на nginx 1.31.5, целевого файла на диске нет ни в одном случае.
Это два разных сервера, у обоих root /www. Сервер А:
# А: цель попадает В ТОТ ЖЕ location
location / { try_files $uri /index.html; }
curl -so /dev/null -w '%{http_code}\n' localhost/report
500
-o /dev/null выбрасывает тело ответа (/dev/null - «файл», который глотает
всё), -w печатает код. Запрос не нашёл файла, ушёл на /index.html, попал в
тот же location /, снова не нашёл, снова ушёл... nginx считает круги, на
десятом сдаётся и отвечает 500. Предел зашит в сам nginx, настройкой он не
меняется:
2026/09/10 19:49:50 [error] 20#20: *9 rewrite or internal redirection cycle while internally redirecting to "/index.html", client: 172.17.0.1, server: , request: "GET /report HTTP/1.1", host: "localhost"
Сервер Б, где location / нет вовсе:
# Б: цель попадает в ДРУГОЙ location
location /app/ { try_files $uri /netu.html; }
curl -so /dev/null -w '%{http_code}\n' localhost/app/report
404
Цель ушла за пределы /app/, там try_files нет, и nginx честно поискал
файл по общим правилам сервера:
2026/09/10 19:53:56 [error] 9#9: *2 open() "/www/netu.html" failed (2: No such file or directory), client: 127.0.0.1, server: , request: "GET /app/report HTTP/1.1", host: "localhost"
Если бы в сервере Б был ещё и location / из сервера А, цель попала бы в
него - и снова цикл: замер с обоими блоками в одном сервере дал 500 на оба
адреса.
Ошибка одна и та же - незадеплоенный или неверно названный файл, - а симптомы
противоположные: в одном случае 500, в другом 404. Оба лечатся не переписыванием
конфига, а взглядом в error_log: он в обоих случаях называет точный файл.
Разбор: где наивное правило подводит
Наивное правило: «в этом location стоит proxy_pass, значит запросы уходят на
бэкенд».
Если в одном блоке есть и try_files, и proxy_pass, то до бэкенда дело
дойдёт только через последний аргумент. И вписать адрес прямо в try_files
нельзя - назначением может быть URI, код или имя, но не адрес. Поэтому связка
всегда выглядит так:
location / {
try_files $uri @backend;
}
location @backend {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
proxy_set_header задаёт заголовок запроса к бэкенду (а add_header -
заголовок ответа клиенту); здесь приложение узнаёт имя сайта. Подробнее -
в разделе про заголовки проксиРазбирается в разделе 7, глава «Заголовки: Host, X-Real-IP, X-Forwarded-*»: Что бэкенд перестаёт видеть.
Второе, о чём легко забыть: каждый кандидат стоит обращения к диску.
try_files $uri $uri/ $uri.html $uri/index.html /fallback.html - это до
четырёх системных вызовов на каждый запрос, которые ничего не найдут, прежде
чем дело дойдёт до пятого варианта. На нагруженном сайте список кандидатов
держат коротким, а не «на все случаи жизни».
Что ломается без этого
- 500 на живом сайте с целым конфигом. Ищи
rewrite or internal redirection cycleвerror_log: цельtry_filesвозвращается в тот же блок, а файла нет. - 404 вместо ожидаемой заглушки. Цель ушла в другой location. Тот же дефект, другой симптом.
- Запросы не доходят до бэкенда. В блоке есть
try_files, и назначение у неё - не@имя. nginx -tругается наtry_filesс одним аргументом. Директива без назначения бессмысленна; это единственная ошибка главы, которую поймает проверка конфига.
Зачем это в работе
try_files - главная директива отдачи статики, и почти всё, что с ней
случается, разбирается по одному вопросу: что тут кандидат, а что
назначение. Задавать его надо, глядя на последний аргумент, а не на первый.
Практическая привычка: при разборе чужого конфига читай try_files справа
налево. Последний аргумент говорит, чем всё кончится, - вернётся код, уйдёт
запрос на бэкенд или начнётся новый круг. Остальные аргументы после этого
читаются как список «где сначала поискать», и их порядок обычно очевиден.
Теперь сам
1. Что сделает try_files $uri $uri/ /index.html =404;?
Ничего хорошего: назначением станет =404, а /index.html окажется
кандидатом - то есть будет проверяться на диске. Назначение всегда одно и
всегда последнее.
2. Почему try_files $uri; не соберётся?
Директиве нужно минимум два аргумента: без назначения она не отвечает на
вопрос «а если не нашлось». Это единственная ошибка из главы, которую поймает
nginx -t, - все остальные проявляются только на живых запросах.
3. В блоке location /images/ { try_files $uri /placeholder.png; } файл
placeholder.png лежит в корне сайта. Что вернёт запрос на несуществующую
картинку?
Заглушку - и код 200. Внутреннее перенаправление уходит на /placeholder.png,
попадает в location / (в /images/ этот URI не начинается), файл там есть.
Обрати внимание на код: клиент получит 200, а не 404, - для картинок это
обычно и нужно, для API было бы неприятным сюрпризом.
Главное
Все аргументы try_files, кроме последнего, - кандидаты, они проверяются на
диске. Последний - назначение, оно не проверяется: =код, @имя или URI.
Назначение-URI начинает обработку заново, и если новый URI попадает в тот же
try_files - получается цикл и 500, а если в другой location - обычный 404.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий