Чому статику краще віддавати напряму
Картинки, 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 і увімкнути підтримку 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, фактично назавжди, для всіх статичних файлів сайту?
Це виправдано лише для файлів, у яких назва змінюється разом із вмістом — наприклад, із міткою версії чи хешем у назві, — для них практично безстроковий кеш і є сенсом цієї директиви. Для звичайної назви файлу, яку просто перезаписують на місці, дуже довгий кеш перетворюється на застарілий вміст, що застряг у браузерах відвідувачів задовго після оновлення сервера.