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

> Как настроить быструю отдачу статики в Nginx: директивы try_files, expires, sendfile, tcp_nopush и кэширование в браузере.

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

Картинки, 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` для статики снижает нагрузку на диск: незачем писать в лог тысячи запросов картинок и скриптов.

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

| Директива | Значение | Эффект |
| --- | --- | --- |
| expires | 30d, 1y | Заголовок Cache-Control и Expires для браузера |
| sendfile | on | Отдача файла через системный вызов sendfile, без копирования в память Nginx |
| tcp\_nopush | on | Отправка заголовков и начала файла одним TCP-пакетом |
| open\_file\_cache | max=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](https://www.zevshost.net/ru/kb/nginx-gzip-brotli/) и включить [поддержку HTTP/2 и HTTP/3](https://www.zevshost.net/ru/kb/nginx-http2-http3-quic/) — вместе с try\_files и expires это даёт основной прирост скорости сайта.

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

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

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

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

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

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

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

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

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

---

Источник: https://www.zevshost.net/ru/kb/nginx-otdacha-statiki/
