Оптимизация статики: sendfile, gzip, кеш и expires
Статика и так быстрая, но горстка директив делает её молниеносной: меньше CPU, меньше трафика, меньше повторных запросов.
«Сожми текст, отдавай через ядро, скажи браузеру кешировать на год — и твой сервер выдохнет.»
Базовая отдача файлов работает из коробки. Но чтобы выжать максимум, нужно настроить три вещи: эффективную отправку байтов, сжатие и кеширование. Разберём по порядку.
sendfile и TCP-оптимизации
http {
sendfile on; # отдача файла напрямую ядром (zero-copy)
tcp_nopush on; # склеить заголовки и начало файла в один пакет
tcp_nodelay on; # не задерживать мелкие пакеты (отключить Nagle)
}
sendfile переносит данные с диска в сеть силами ядра, минуя копирование в пользовательскую память — заметно экономит CPU. tcp_nopush работает в паре с ним: отправляет HTTP-заголовки и первый кусок файла одним пакетом. tcp_nodelay важен для множества мелких ответов.
Сжатие
gzip on;
gzip_comp_level 5;
gzip_min_length 256; # не жать крошечные файлы
gzip_types text/plain text/css application/json
application/javascript text/xml application/xml;
gzip_vary on; # корректный кеш на прокси/CDN
Сжатие уменьшает текстовые ответы в разы. gzip_min_length 256 отключает сжатие для совсем мелких файлов (накладные расходы gzip сделают их больше). Картинки (JPEG/PNG) и так сжаты — их не трогаем. Brotli даёт на 15–25% лучше, если установлен модуль.
Кеширование браузером
location ~* \.(css|js|jpg|jpeg|png|gif|svg|woff2)$ {
expires 1y; # Expires + Cache-Control: max-age
add_header Cache-Control "public, immutable";
access_log off; # не логировать каждую иконку
}
expires 1y; ставит и Expires, и Cache-Control: max-age. immutable говорит браузеру «этот файл не изменится, даже не перепроверяй» — идеально для версионированных ассетов вроде app.7f3a.js.
Кеш файловых дескрипторов
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
Как работает под капотом
open_file_cache запоминает открытые дескрипторы файлов, их размеры и время изменения. При повторной отдаче того же файла Nginx не делает заново open() и stat() — операции ввода-вывода резко сокращаются. Для популярной статики это ощутимый выигрыш. Сжатие gzip выполняется на лету в воркере, но результат можно и закешировать (gzip_static с заранее сжатыми .gz-файлами).
Частые ошибки
- Жать уже сжатое. Включать gzip для JPEG/PNG/видео — пустая трата CPU.
- immutable без версионирования имён. Если файл
style.cssне меняет имя при обновлении,immutableзаставит браузеры держать старую версию. Версионируй имена (хеш в названии). - Логировать каждую иконку. Раздутый access.log из-за статики;
access_log offдля ассетов спасает.
Best practices
- Версионируй имена статики (хеш) +
expires 1y; immutable— пользователь скачает файл один раз. - Сжимай только текстовые типы; не трогай уже сжатые форматы.
- Включи
open_file_cacheна серверах с тяжёлой статикой.
Стратегия кеширования: что и насколько
Грамотное кеширование требует разной политики для разного контента — единый expires на всё только навредит. Версионированные ассеты (файлы с хешем в имени, вроде main.a1b2c3.js) можно кешировать максимально агрессивно: expires 1y; immutable, потому что при любом изменении меняется имя, и старая версия никогда не «застрянет». HTML-страницы, наоборот, кешировать надолго опасно: пользователь должен сразу видеть свежий контент, поэтому им ставят короткий срок или no-cache. Картинки и шрифты — где-то посередине, обычно недели или месяцы.
Стоит понимать и разницу между кешем браузера и кешем самого Nginx. Всё, о чём шла речь (expires, Cache-Control), управляет кешем в браузере пользователя — это убирает повторные запросы вообще. Но Nginx умеет и кешировать ответы бэкенда у себя через proxy_cache: тогда популярную динамическую страницу он отдаёт из своего кеша, не дёргая приложение каждый раз. Это мощный приём для контентных сайтов, но требует аккуратности с инвалидацией — нельзя показать пользователю чужие данные из кеша. Начинать стоит с простого: версионируй статику, кешируй её на год, сжимай текстовые типы — этого уже достаточно, чтобы ощутимо разгрузить сервер и ускорить загрузку страниц для повторных посетителей.
Итоги
Скорость статики складывается из sendfile + TCP-настроек (меньше CPU и пакетов), сжатия текстовых типов (меньше трафика), агрессивного кеширования браузером через expires/immutable (меньше повторных запросов) и open_file_cache (меньше дискового I/O). Раздачу статики освоили — переходим к главной суперсиле Nginx: обратному прокси.