Отдача статики в Nginx: как настроить без ошибок
Nginx может отдавать CSS, JavaScript, изображения и шрифты напрямую, без участия приложения. Разбираем root и alias, приоритеты location, кеширование, gzip и ошибки, из-за которых статика внезапно превращается в 404.

Nginx обычно справляется со статикой быстрее и дешевле приложения, но одна ошибка в location превращает рабочий CSS в 404, а неправильный Cache-Control заставляет браузеры скачивать один и тот же файл при каждом открытии страницы. Ниже — практическая схема для Nginx 1.25+ и типичного сайта, где файлы лежат на диске, а динамические запросы уходят в отдельный upstream.
Сначала определите, что именно должен делать Nginx
Для статических файлов у Nginx есть простая задача: сопоставить URL с путём на диске, проверить наличие файла и вернуть его клиенту. Приложение при этом не вызывается.
Например, URL /static/css/site.css может соответствовать файлу /srv/www/static/css/site.css. Динамический URL вроде /catalog/42 при этом передаётся в приложение через proxy_pass или другой механизм.
До настройки проверьте:
- версию Nginx командой
nginx -v; - реальный путь к каталогу со статикой;
- права пользователя, от имени которого работает Nginx, на чтение каталогов и файлов;
- не перекрывает ли нужный URL другой
location; - какие имена получают файлы после сборки фронтенда: постоянные или с хешем в имени.
Правильная отдача статики начинается не с заголовка кеширования, а с однозначного соответствия: URL должен указывать ровно на тот файл, который вы готовы открыть наружу.
Выберите между root и alias
Оба директивных блока могут отдавать статические файлы, но они строят путь по-разному. Это самая частая причина неожиданных 404.
| Подход | Как строится путь | Когда выбирать | Риск ошибки |
|---|---|---|---|
root | К значению root добавляется весь URI | Когда структура URL повторяет структуру каталогов | Средний: легко забыть часть URI в пути |
alias | Префикс location заменяется указанным каталогом | Когда URL и путь на диске не совпадают | Высокий без завершающего слеша |
| Проксирование в приложение | Файл читает приложение или его веб-сервер | Только когда файл нельзя отдавать напрямую | Лишняя нагрузка и более сложная диагностика |
Когда подходит root
Если URI /static/css/site.css должен указывать на /srv/www/static/css/site.css, конфигурация выглядит так:
server {
listen 80;
server_name example.com;
root /srv/www;
location /static/ {
try_files $uri =404;
}
}
Здесь Nginx берёт значение /static/css/site.css, добавляет его к /srv/www и проверяет файл /srv/www/static/css/site.css. Если файла нет, клиент получает 404, а не ответ приложения.
Когда нужен alias
Допустим, URL должен остаться /assets/, а файлы физически лежат в /data/releases/current/public/. Тогда используйте alias:
server {
listen 80;
server_name example.com;
location /assets/ {
alias /data/releases/current/public/;
try_files $uri =404;
}
}
У alias в таком варианте важны завершающие слеши у location и у пути. Nginx заменяет префикс /assets/ на каталог из alias. Поэтому запрос /assets/app.css попадёт в файл /data/releases/current/public/app.css.
Термин: upstream — группа серверов или приложение, куда Nginx передаёт запросы. Для статики upstream обычно не нужен: Nginx читает файл сам.
Разделите статику и динамические запросы
Не стоит помещать всю обработку в один универсальный location /, а затем надеяться, что Nginx сам поймёт, где файл, а где маршрут приложения. Явный блок для статики проще проверять и безопаснее кешировать.
server {
listen 80;
server_name example.com;
root /srv/www;
location ^~ /static/ {
try_files $uri =404;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Префикс ^~ сообщает Nginx: если найден этот префиксный location, не проверяй регулярные выражения ниже. Это особенно полезно, когда в конфигурации есть общий блок вроде location ~* \\.(php|html)$ или другие регулярные правила.
Директива try_files $uri =404 не позволяет отправить отсутствующий CSS или картинку в приложение. Это сокращает лишние запросы и помогает сразу отличить ошибку пути от ошибки backend.
Настройте кеширование по типу имени файла
Долгое кеширование безопасно только для файлов, URL которых меняется после изменения содержимого. Например, сборщик создаёт app.8f31c2.js. При новой сборке имя меняется, поэтому старый файл можно кешировать на год.
location ^~ /static/ {
try_files $uri =404;
location ~* \\.(?:css|js|jpg|jpeg|png|gif|svg|webp|woff2?)$ {
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
}
Такой вложенный regex-location требует внимательной проверки всей конфигурации. Если структура проекта сложнее, безопаснее разделить каталоги для версионируемых и обычных файлов:
location ^~ /static/build/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
location ^~ /static/uploads/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=3600" always;
}
Для загруженных пользователями файлов годовой кеш обычно опасен: файл может быть заменён по тому же URL. Если вы не контролируете изменение имён, начинайте с короткого времени кеша или отправляйте заголовки без долгого max-age.
Добавьте gzip только там, где он действительно работает
Nginx умеет сжимать текстовые ответы на лету через модуль gzip. Но при включённом сжатии CPU будет тратиться на каждый запрос, если файл не подготовлен заранее. Для редко меняющихся CSS и JavaScript практичнее собрать рядом предварительно сжатую копию и включить модуль gzip_static.
http {
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1000;
gzip_static on;
server {
listen 80;
server_name example.com;
root /srv/www;
location ^~ /static/ {
try_files $uri =404;
}
}
}
При запросе к app.js Nginx сможет использовать файл app.js.gz, если он существует и клиент сообщил, что принимает gzip. Сам файл .gz должен быть создан заранее вашим сборщиком или отдельным этапом деплоя. Проверьте, что модуль доступен в конкретной сборке Nginx: nginx -V показывает параметры сборки.
Не сжимайте изображения, архивы и уже сжатые форматы ради самого факта сжатия. Выигрыша обычно не будет, а CPU и задержка останутся.
Что чаще всего ломается
Файл есть, но Nginx отвечает 404
Проверьте итоговый путь. При root /srv/www запрос /static/app.css ищется как /srv/www/static/app.css. При alias /srv/www/static/ тот же URL внутри location /static/ ищется как /srv/www/static/app.css, потому что префикс заменяется.
После изменения файла браузер показывает старую версию
Посмотрите заголовок Cache-Control в инструментах разработчика. Если на файле без хеша установлен max-age=31536000, браузер имеет право не обращаться к серверу год. Исправьте стратегию имён или временно уменьшите время кеша.
Вместо 404 открывается главная страница
Такое бывает в SPA-конфигурациях, где неизвестный путь намеренно отправляется в index.html. Для каталога статики оставьте try_files $uri =404 и не используйте fallback на HTML внутри этого location.
Конфигурация не применялась
Перед перезагрузкой проверяйте синтаксис командой nginx -t. Она покажет ошибку и путь к конфигурации. После успешной проверки примените изменения штатной перезагрузкой сервиса, принятой в вашей системе.
Nginx не может прочитать файл
Ошибка 403 часто связана не с самим файлом, а с отсутствием права прохода на одном из родительских каталогов. Проверяйте пользователя Nginx и права на весь путь. Не делайте весь каталог доступным для записи веб-процессу: для отдачи достаточно права чтения файла и прохода по каталогам.
Когда какой подход выбирать
- Используйте
root, если URL повторяет структуру каталога на диске. - Используйте
alias, если публичный URL не должен раскрывать внутреннюю структуру хранения. - Добавляйте
try_files $uri =404, если отсутствующий файл не должен попадать в приложение. - Ставьте долгий кеш только для файлов с хешем или версией в имени.
- Выносите статику из proxy-маршрута, если приложение не выполняет специальную обработку файла.
Что проверить после настройки
Начните с трёх запросов: существующий CSS должен вернуть 200, отсутствующий файл — 404, а большой версионируемый JavaScript — ожидаемый Cache-Control. Затем проверьте ответ с включённым gzip и без него, права доступа к каталогу и фактический путь в логах Nginx.
После выкладки смотрите не только на среднее время ответа. Полезнее отдельно отслеживать долю 404 для статики, объём переданных байтов, количество запросов к приложению за отсутствующими файлами и частоту cache hit на CDN или reverse proxy. Инвалидация CDN — отдельная тема; здесь достаточно убедиться, что Nginx отдаёт корректный ответ и правильные заголовки.
Редактор изображений: Убрать лишнее, заменить фон, добавить объект, расширить кадр — на вашем изображении.
Спросить за 20 ₽Источники: Официальная документация Nginx: ngx_http_core_module, Официальная документация Nginx: ngx_http_gzip_static_module
Частые вопросы
Что выбрать для статики: root или alias?
Почему Nginx отдаёт 404, хотя файл существует?
Можно ли поставить статику на годовой кеш?
Зачем нужен try_files $uri =404?
Почему gzip_static не использует файл .gz?
Что делать, если у меня другая версия Nginx?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.