До основного вмісту

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

Nginx · · 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, фактично назавжди, для всіх статичних файлів сайту?

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

Чи була стаття корисною?
← Назад до бази знань Поставити питання підтримці