К основному содержимому

Отдача статики в Nginx: try_files, expires и sendfile

Nginx · 29.09.2026 · 4 мин чтения
Иллюстрация к статье «Отдача статики в Nginx: try_files, expires и sendfile»

Почему статику лучше отдавать напрямую

Картинки, CSS, JS и шрифты не требуют выполнения PHP-кода, поэтому пропускать их через PHP-FPM — трата ресурсов. Nginx умеет отдавать файлы с диска напрямую, в разы быстрее и с меньшей нагрузкой на сервер. Задача администратора — правильно указать Nginx, какие пути обслуживать напрямую, а какие передавать бэкенду.

Как работает try_files

Директива try_files проверяет файлы и каталоги по порядку и отдаёт первый найденный, а если ничего не найдено — передаёт запрос дальше:

location / {
    root /var/www/example.com/public;
    try_files $uri $uri/ /index.php?$query_string;
}

Здесь Nginx сначала ищет файл с точным именем запроса, потом каталог с таким именем, и только если ничего не нашлось — передаёт запрос в index.php. Это стандартная схема для WordPress, Laravel и большинства современных PHP-приложений.

Как отдавать статику отдельным location

Для статических файлов имеет смысл выделить отдельный блок с собственными настройками кэширования:

location ~* \.(jpg|jpeg|png|gif|webp|svg|css|js|woff2?)$ {
    root /var/www/example.com/public;
    try_files $uri =404;
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Параметр access_log off для статики снижает нагрузку на диск: незачем писать в лог тысячи запросов картинок и скриптов.

Директивы кэширования и производительности

ДирективаЗначениеЭффект
expires30d, 1yЗаголовок Cache-Control и Expires для браузера
sendfileonОтдача файла через системный вызов sendfile, без копирования в память Nginx
tcp_nopushonОтправка заголовков и начала файла одним TCP-пакетом
open_file_cachemax=1000 inactive=20sКэш дескрипторов файлов и метаданных, чтобы не читать диск повторно

Директивы sendfile и tcp_nopush включают в секции http {} один раз — они действуют глобально и не требуют настройки на каждый сайт.

Как настроить open_file_cache

На сайтах с большим числом мелких файлов — иконки, аватары, миниатюры — полезно включить кэш дескрипторов:

open_file_cache max=2000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

Это уменьшает число системных вызовов open() и stat() для файлов, которые запрашивают часто: Nginx хранит их дескрипторы в памяти вместо повторного чтения с диска.

Частые ошибки при отдаче статики

Что чаще всего ломает раздачу статики:

  • слишком долгий expires для файлов без версионирования в имени — обновлённый CSS не долетит до пользователя
  • регулярное выражение в location без экранирования точки — совпадёт с лишними путями
  • отсутствие try_files $uri =404; — запрос на несуществующий файл уйдёт в PHP-FPM зря
  • root, указанный в двух разных местах конфигурации с разными путями — типичная причина 404 на статике

Итог

Чек-лист для быстрой отдачи статики:

  • вынести статические расширения в отдельный location с try_files $uri =404;
  • задать expires от 30 дней и Cache-Control: immutable для файлов с версией в имени
  • включить sendfile, tcp_nopush и open_file_cache в http {}
  • отключить access_log для статики, чтобы не грузить диск

После настройки статики стоит проверить сжатие ответов через gzip и Brotli и включить поддержку HTTP/2 и HTTP/3 — вместе с try_files и expires это даёт основной прирост скорости сайта.

Частые вопросы

Если отключить access_log для статики, означает ли это, что ошибки по этим файлам тоже перестанут попадать в журнал?

Нет, access_log off убирает из журнала только обычные успешные запросы, а журналирование ошибок для этого location продолжает работать отдельно — оно управляется своей директивой error_log. Отключение access_log просто снижает нагрузку на диск от потока рядовых запросов картинок, скриптов и стилей, а не лишает вас возможности видеть настоящие сбои.

Если CSS-файл обновили, а expires задан на долгий срок, как сделать так, чтобы посетители получили именно новую версию?

Само по себе изменение значения expires не поможет: браузеры, уже закэшировавшие файл по старому правилу, продолжат использовать его, пока собственный срок кэша не истечёт. Надёжный способ — дать файлу другое имя или добавить метку версии, обычно на этапе сборки, чтобы изменённый файл запрашивался по новому адресу, а не полагаться на естественное истечение старого кэша.

Может ли одно и то же расширение файла обслуживаться сразу в нескольких блоках location, или Nginx сообщит о конфликте между ними?

Nginx сам по себе о конфликте не сообщает — он просто выбирает один подходящий блок location по собственным правилам совпадения, обычно отдавая предпочтение самому точному совпадению. Пересекающиеся блоки для одного класса расширений чаще оказываются ошибкой конфигурации, чем осознанным решением, и приводят к путанице вроде несовпадающих заголовков кэширования у внешне одинаковых запросов.

Безопасно ли ставить очень долгий expires, практически навсегда, для всех статических файлов сайта?

Это оправдано только для файлов, у которых имя меняется вместе с содержимым — например, с меткой версии или хешем в названии, — для них фактически бессрочный кэш и есть смысл директивы. Для обычного имени файла, которое просто перезаписывают на месте, очень долгий кэш превращается в устаревшее содержимое, застрявшее в браузерах посетителей надолго после обновления сервера.

Статья помогла?
← Назад в базу знаний Задать вопрос поддержке