# Віддача статики в 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/uk/kb/nginx-gzip-brotli/) і увімкнути [підтримку HTTP/2 та HTTP/3](https://www.zevshost.net/uk/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/uk/kb/nginx-otdacha-statiki/
