В Nginx три директивы — location, try_files и index — чаще всего становятся причиной «магических» 404 и неожиданных редиректов. Обычно проблема не в том, что «Nginx сломан», а в том, что мы неверно представляем порядок принятия решения: какой location выбран, какой путь считается файловым, и когда Nginx пытается подставить индексный файл.
Ниже — практический разбор: как устроен приоритет выбора location, что делает try_files, как работает index, где всплывают root/alias и почему rewrite часто усложняет отладку. В конце — рабочие шаблоны для статики, PHP и SPA-роутинга.
Как Nginx выбирает location: приоритет без мифов
Когда приходит запрос, Nginx сначала выбирает блок server (по listen и server_name), а затем внутри него подбирает один location. Это ключ к диагностике 404: выбранный location определяет и root/alias, и наличие try_files/index, и любые rewrite.
Краткий порядок выбора location
В реальных конфигах важнее всего помнить про тип совпадения:
location = /exact— точное совпадение, самый высокий приоритет.location ^~ /prefix/— префикс с запретом дальнейшего выбора regex-location (если префикс совпал).location /prefix/— обычный префикс: выбирается самый длинный совпавший префикс, но затем Nginx ещё может проверить regex.location ~ regexиlocation ~* regex— регулярки (с учётом регистра и без). Если regex совпал, он выигрывает у обычного префикса.
Практическая ловушка: вы ожидаете, что запрос пойдёт в location / с try_files, а его перехватывает location ~ \.php$ или другая регулярка — и дальше логика уже совсем другая.
Как быстро понять, какой location сработал
Если есть доступ к конфигу и логам, самый быстрый путь — временно включить более подробные логи и убедиться, что вы правите именно тот блок, который обслуживает запрос. На практике обычно достаточно:
- посмотреть
error_log(часто хватает уровняinfo); - проверить, нет ли regex-location, который «перехватывает» запрос;
- временно упростить конфиг: отключить подозрительные regex-локации и проверить, исчезает ли 404.
Если вы видите 404 «на ровном месте», сначала докажите себе, что запрос реально попадает в тот
location, в котором вы правитеtry_filesиroot/alias.
try_files: что именно проверяется и почему это главный анти-404
try_files — это последовательная проверка «существует ли файл/директория» с выбором первого удачного варианта. Если ни один вариант не найден — выполняется последний аргумент (обычно это URI на обработчик или явный код ошибки).
Ключевой момент: try_files проверяет файловую систему, используя текущие root/alias и значение URI. Поэтому любая ошибка в путях или в выборе location мгновенно превращается в 404.
Базовый паттерн для статики
Классический безопасный вариант:
location / {
try_files $uri $uri/ =404;
}
$uri— пробуем отдать файл как есть.$uri/— пробуем директорию (полезно для «человеческих» URL и работыindex).=404— если ничего не нашли, отдаём 404 явно, без неявных редиректов.
Типовая связка с index
index срабатывает тогда, когда запрос указывает на директорию (реальную или «разрешённую» через try_files $uri/). Тогда Nginx пытается найти один из индексных файлов.
server {
root /var/www/site/public;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
Если забыть $uri/, запрос /docs/ может не перейти в режим «директории», и index просто не будет применён.

try_files для PHP: правильная идея и частая ошибка
Во многих PHP-приложениях нужен fallback на index.php (front controller). Важно: fallback держите в «корневом» location /, а обработчик PHP делайте отдельным location для реальных .php — иначе легко получить петли и неочевидные статусы.
server {
root /var/www/site/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}
Обратите внимание на fastcgi_param SCRIPT_FILENAME: ошибка в этой строке — один из самых частых источников ситуации «404, но файл же есть». На самом деле PHP-FPM получает путь не к тому файлу.
Чем try_files лучше rewrite (и где начинаются ловушки)
rewrite меняет URI по правилам регулярных выражений. Это полезно, но часто становится источником проблем:
- непредсказуемые цепочки переходов из-за флагов (
last,break,redirect); - лишняя стоимость на большом числе regex;
- сложнее отладка: URI переписали — и уже неясно, какой путь реально проверяется на диске.
Для типового сценария «если файл существует — отдай, иначе — в контроллер» почти всегда достаточно try_files, и это заметно прозрачнее.
index: как Nginx ищет индексный файл на самом деле
index — это список имён файлов, которые Nginx пытается открыть, когда URI указывает на директорию. По умолчанию часто встречается index index.html или index index.php index.html.
Три практические детали:
- index применяется только для директории: реальной или «разрешённой» через
try_files $uri/. - порядок важен: если первым стоит
index.php, то даже при наличииindex.htmlбудет выбран PHP (если файл существует и PHP-локация его обработает). - листинг не включается сам: если индекса нет, вы получите 403/404 (по ситуации), но не список файлов, пока явно не включите
autoindex on.
Если вы мигрируете сайт на новый сервер или меняете структуру путей, полезно держать под рукой чеклист про редиректы и HTTPS, чтобы не «потерять» страницы в процессе: 301-редиректы, HSTS и SSL при переносе домена.
root и alias: почему это вечный источник 404
root и alias выглядят похоже, но ведут себя по-разному:
rootдобавляет URI к пути: итоговый файл =root+ URI.aliasзаменяет совпавшую частьlocationна путь alias.
Когда нужен root
Если вы хотите, чтобы структура URL повторяла структуру каталогов под root, используйте root.
location /static/ {
root /var/www/site/public;
try_files $uri =404;
}
Запрос /static/app.js будет искать файл /var/www/site/public/static/app.js.
Когда нужен alias (и где чаще ошибаются)
alias удобен, когда URL не должен включать часть пути на диске.
location /static/ {
alias /var/www/site/assets/;
try_files $uri =404;
}
Теперь запрос /static/app.js ищет /var/www/site/assets/app.js.
Ловушка №1: слеши. Для location, оканчивающегося на /, alias почти всегда тоже должен оканчиваться на /, иначе пути будут склеиваться неожиданно.
Ловушка №2: регулярки и переписывание URI. Конструкции с alias хуже переносят усложнение логики, и в спорных случаях проще нормализовать структуру каталогов и вернуться к root.
SPA routing: как отдавать index.html и не ломать статику
Для SPA (React/Vue/Angular) маршрутизация живёт на клиенте, а сервер должен:
- отдавать реальные файлы (JS/CSS/картинки) как есть;
- на все «нефайловые» URL отдавать
/index.html.
Самый простой и безопасный вариант — fallback на /index.html через try_files:
server {
root /var/www/spa/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
Если пользователь открывает /account/settings, файла такого нет — Nginx отдаёт index.html, а роутер SPA уже внутри браузера отображает нужную страницу.
Отдельно: 404 для отсутствующих ассетов
Иногда нужно, чтобы «битые» ассеты не маскировались под 200 с index.html (иначе браузер может закешировать HTML вместо JS). Для этого обычно выносят ассеты в отдельный префикс:
location /assets/ {
try_files $uri =404;
expires 30d;
}
А общий location / оставляют с fallback на /index.html.
Типовые 404 в Nginx: чеклист диагностики
Когда вы ловите 404, удобно проверять по порядку:
- Тот ли server? Совпадает ли
server_name, нет лиdefault_server, который перехватывает домен. - Тот ли location? Нет ли regex, который выигрывает у префикса. Помните про
^~и=. - Правильный ли файловый путь? Сверьте
root/aliasи реальное расположение файла на диске. - Не ломает ли fallback? Частая ошибка — fallback на URI, который снова попадает в тот же
locationи не находит файл. - Для PHP: проверьте
SCRIPT_FILENAME,fastcgi_pass, наличие файла и права.
Готовые шаблоны: что ставить «по умолчанию»
Статический сайт с красивыми URL и явным 404
server {
root /var/www/site/public;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}

PHP-приложение с фронт-контроллером (index.php)
server {
root /var/www/app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
location ~ /\. {
return 404;
}
}
SPA с отдельным 404 для ассетов
server {
root /var/www/spa/dist;
index index.html;
location /assets/ {
try_files $uri =404;
expires 30d;
}
location / {
try_files $uri $uri/ /index.html;
}
}
Итоги: как перестать «лечить 404» переписыванием
Если держать в голове три опоры — выбор location, корректный root/alias и понятный try_files — большинство проблем исчезает без сложных rewrite.
Минимальный практический совет: сначала сделайте «прозрачный» конфиг, который отдаёт файлы и директории через try_files, и только потом добавляйте regex-location и rewrite. Так вы быстро локализуете, где именно появляется расхождение между URI и реальным путём на диске.
Если вы размещаете несколько проектов и хотите меньше возиться с конфликтами портов, разными версиями PHP и отдельными Nginx-конфигами, часто удобнее выделить проекту отдельную VDS или использовать виртуальный хостинг под более простой стек.


