Перезапустили сервер — и всё. Сайт молчит, браузер показывает белую страницу с цифрами «404». Или другая история: переехали на новый VPS, перенесли файлы, запустили Nginx — работает половина сайта, вторая половина в 404. Обе ситуации лечатся за пять минут. Но только если смотреть в правильное место.
Первым делом — error.log
Прежде чем что-то трогать в конфиге — открыть лог. Nginx уже знает в чём дело и написал об этом:
sudo tail -f /var/log/nginx/error.log
Строка вида:
open() "/var/www/html/about" failed (2: No such file or directory)
говорит всё: сервер полез за файлом about по пути /var/www/html/ — и не нашёл. Половина диагностики уже сделана.
Если виртуальные хосты с раздельными логами:
sudo tail -f /var/log/nginx/yourdomain.com.error.log
Неверный путь — и сервер слепнет
На VPS чаще всего виновата директива root. Одна буква не там — и Nginx ищет файлы в несуществующей папке:
sudo nano /etc/nginx/sites-available/yourdomain.com
Убедиться что директория вообще существует:
ls -la /var/www/mysite/public
Директория есть, 404 остаётся? Дело в правах. Nginx на Debian и Ubuntu работает от имени www-data, и если папка принадлежит другому пользователю — сервер просто не видит содержимое:
sudo chown -R www-data:www-data /var/www/mysite
sudo chmod -R 755 /var/www/mysite
SPA и CMS: почему вложенные маршруты дают 404
Страница / открывается, /catalog/item — нет. Знакомо? У React, Vue, Angular и большинства CMS (WordPress, Laravel) маршруты виртуальные: файла catalog/item на диске нет. Nginx честно об этом сообщает.
Решается одной директивой try_files. Без неё приложение просто не работает:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
Для статической сборки React или Vue:
location / {
try_files $uri $uri/ /index.html;
}
Логика такая: сначала ищем файл точно по пути, затем папку, затем — если ничего нет — отдаём точку входа. Без этого try_files любой переход по ссылке внутри SPA даёт 404.
Конфликт блоков location
Nginx читает location-блоки по своим правилам приоритетов, и бывает что один блок перехватывает запросы которые предназначались другому. Типичная жертва — API-роуты, которые попадают в блок статики:
location ~* \.(js|css|png|jpg)$ {
expires 30d;
}
Если паттерн написан неаккуратно — он может «поймать» /api/users. Nginx полезет искать файл api/users на диске и вернёт 404.
Проверить синтаксис конфига:
sudo nginx -t
Проверить ответ по конкретному адресу:
curl -I https://yourdomain.com/api/users
PHP-FPM: сокет не тот
Если сайт на PHP и 404 только на .php-файлах — скорее всего дело в сокете. Конфиг указывает один путь, реальный сокет лежит в другом месте:
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Посмотреть что реально лежит в папке:
ls /var/run/php/
Убедиться что PHP-FPM вообще работает:
sudo systemctl status php8.1-fpm
Несовпадение пути к сокету — одна из самых частых причин 404 после обновления PHP или переезда конфига с другого сервера.
Нет индексного файла в директории
Nginx не умеет «угадывать» что отдать если в папке нет индексного файла. Запрос на /about/ без index.html внутри — 404:
location / {
index index.html index.php;
}
Для файловых серверов где нужен листинг:
location /downloads/ {
autoindex on;
}
Своя страница вместо стандартного 404
Дефолтная страница Nginx с белым полем — не лучшее что можно показать посетителю. Плюс она отдаёт версию сервера, что лишняя информация для посторонних:
server {
error_page 404 /404.html;
location = /404.html {
root /var/www/html;
internal;
}
}
internal здесь принципиален: он закрывает прямой доступ к файлу снаружи — страница отдаётся только как ошибка, не как обычный URL.
Если 404 приходит от бэкенда (Node.js, Python), а нужно показать свою страницу:
proxy_intercept_errors on;
error_page 404 /custom_404.html;
Редирект вместо 404 — правильное решение при переезде
Сайт переехал, структура URL изменилась. Правильный ответ здесь не «починить 404», а настроить 301. Поисковик обновит индекс, пользователь попадёт куда нужно:
location = /old-page {
return 301 /new-page;
}
Массовый редирект по шаблону:
rewrite ^/category/(.*)$ /catalog/$1 permanent;
Применить правки без простоя
После изменений — сначала проверка:
sudo nginx -t
Если всё чисто:
sudo systemctl reload nginx
reload перечитывает конфиг не обрывая текущие соединения. Это важно для продакшна — restart прервёт активные запросы.
Что и где смотреть
| Картина | Куда идти |
|---|---|
| 404 на все страницы сразу | Директива root, существование папки |
| Главная открывается, вложенные нет | try_files не настроен |
Только .php файлы дают 404 |
Статус PHP-FPM, путь к сокету |
| После переезда на новый сервер | Права на папку: chown, chmod |
| После редизайна со сменой URL | 301-редиректы через rewrite |
| Первичная диагностика | sudo tail -f /var/log/nginx/error.log |
| Проверить конфиг | sudo nginx -t |
| Применить без простоя | sudo systemctl reload nginx |