1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 1 из 5

Кандидаты и назначение

Коротко

В try_files аргументы до последнего проверяются как кандидаты на диске. Последний задаёт дальнейшее действие, если ни один не найден.

  • Найденный файл обрабатывается в текущем контексте; его путь зависит от root или alias.
  • Последним аргументом можно задать код, именованный location или URI.
  • Переход к URI запускает выбор location заново.
  • Если повторная обработка возвращается к тому же неразрешимому условию, возникает цикл и ответ 500. Проследи каждый переход до конечного ресурса.

Если различие «кандидат / назначение» уже очевидно - листай до «Теперь сам».

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

/etc/nginx/conf.d/shop.confhttp › server
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; кандидаты путь строится по root/alias проверяется наличие на диске первый найденный отдаётся назначение на диске НЕ проверяется =код | @имя | URI URI запускает обработку заново Один аргумент - ошибка конфигурации: директива без назначения бессмысленна, и это единственное, что поймает nginx -t.
Две неравные части. Отсюда и ответ на вопрос из начала урока: =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. Сервер А:

/etc/nginx/conf.d/shop.confhttp › server
# А: цель попадает В ТОТ ЖЕ location
location / { try_files $uri /index.html; }
$  команда
curl -so /dev/null -w '%{http_code}\n' localhost/report
↳  выводcurl -so /dev/null -w '%{http_code}\n' localhost/report
500

-o /dev/null выбрасывает тело ответа (/dev/null - «файл», который глотает всё), -w печатает код. Запрос не нашёл файла, ушёл на /index.html, попал в тот же location /, снова не нашёл, снова ушёл... nginx считает круги, на десятом сдаётся и отвечает 500. Предел зашит в сам nginx, настройкой он не меняется:

↳  выводtail -n 1 /var/log/nginx/error.log
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 / нет вовсе:

/etc/nginx/conf.d/shop.confhttp › server
# Б: цель попадает в ДРУГОЙ location
location /app/ { try_files $uri /netu.html; }
$  команда
curl -so /dev/null -w '%{http_code}\n' localhost/app/report
↳  выводcurl -so /dev/null -w '%{http_code}\n' localhost/app/report
404

Цель ушла за пределы /app/, там try_files нет, и nginx честно поискал файл по общим правилам сервера:

↳  выводtail -n 1 /var/log/nginx/error.log
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, код или имя, но не адрес. Поэтому связка всегда выглядит так:

/etc/nginx/conf.d/shop.confhttp › server
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 запускает обработку заново. Проследи его до конечного ответа: если запрос возвращается к тому же правилу без изменения условий, получится цикл внутренних перенаправлений.

Комментарии

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

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

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