OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

.br/.gz для статики: precompressed в Nginx и Apache без лишних сюрпризов

Предсжатые Brotli и gzip ускоряют отдачу статики и снимают нагрузку с CPU. Покажу настройку Nginx и Apache для корректной выдачи .br/.gz: заголовки, приоритеты, MIME-типы, генерацию артефактов в CI/CD и проверку через curl и DevTools.
.br/.gz для статики: precompressed в Nginx и Apache без лишних сюрпризов

Предсжатие статики (precompressed assets) — простой и надёжный способ ускорить сайт: вы заранее создаёте файлы .br (brotli) и .gz (gzip) для CSS, JS, HTML, SVG и текстовых ресурсов, а веб‑сервер отдаёт их напрямую в зависимости от Accept-Encoding. Такой подход даёт стабильную скорость ответа и экономит CPU по сравнению с динамической компрессией на лету.

Как это работает: Accept-Encoding и Content-Encoding

Браузер сообщает серверу, какие кодеки поддерживает, заголовком Accept-Encoding (например, br, gzip). Сервер выбирает подходящий вариант файла и возвращает его с заголовком Content-Encoding (br или gzip). Чтобы кэш на пути (прокси, CDN, браузер) различал варианты, нужно добавлять Vary: Accept-Encoding. Это критически важно, иначе клиенту, не поддерживающему brotli, может прилететь неподходящий ответ из кэша.

Коротко: если клиент умеет brotli — отдаём .br. Если нет, но есть gzip — отдаём .gz. Если ни то ни другое — исходный файл. Всегда помним про Vary: Accept-Encoding.

Nginx: предпочтительный путь — gzip_static и brotli_static

Для Nginx идеальный вариант — использовать встроенный модуль http_gzip_static_module и модуль brotli (ngx_brotli) с директивой brotli_static. Эти модули корректно решают главную проблему «ручной» раздачи: Content-Type определяется по запрошенному URI, а не по служебному расширению .br/.gz. Иными словами, отдавая /assets/app.css из файла app.css.br, сервер вернёт правильный Content-Type: text/css.

Проверьте модули:

nginx -V 2>&1 | tr ' ' '\n' | grep http_gzip_static_module
nginx -V 2>&1 | tr ' ' '\n' | grep brotli

Базовый фрагмент конфигурации (HTTP-блок или сервер/локация для статики):

# Включаем статическую раздачу предсжатых файлов
gzip on;
gzip_static on;          # использовать *.gz если есть
# brotli требует внешний модуль ngx_brotli
brotli on;
brotli_static on;        # использовать *.br если есть
brotli_comp_level 6;     # разумный компромисс, актуально для on-the-fly
# Типы, которые целесообразно компрессировать
brotli_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_types   text/plain text/css application/javascript application/json image/svg+xml;

# Локация со статикой
location /assets/ {
    root /var/www/site;
    add_header Vary Accept-Encoding always;  # обязательный Vary
    try_files $uri =404;
}

Комментарии:

  • gzip_static on и brotli_static on приоритетно отдают предсжатые файлы. Если их нет, сработает динамическое gzip on/brotli on (если включено), но лучше держать это как страховку, а не основной режим.
  • Не используйте gzip_static always, если у вас нет строгой уверенности, что все клиенты понимают gzip: так вы рискуете отдать сжатый контент клиенту без поддержки gzip.
  • Директивы brotli_types и gzip_types формируют список MIME-типов для компрессии на лету. Для *_static они не обязательны, но их уместно держать синхронизированными с вашей политикой — это лучший «универсальный» шаблон.
  • add_header Vary Accept-Encoding always; нужен даже при статической раздаче, чтобы кэши не путали варианты.

Если brotli-модуль недоступен

Частая ситуация на дистрибутивах или в сборках без ngx_brotli: подключайте только gzip_static on и продолжайте отдавать .gz как precompressed. Это всё равно даёт ощутимую экономию трафика и CPU. Позже можно добавить brotli. На собственном VDS проще контролировать сборку Nginx и подключить ngx_brotli, если он отсутствует в репозитории.

gzip on;
gzip_static on;
# без brotli: директивы brotli* опускаем
location /assets/ {
    root /var/www/site;
    add_header Vary Accept-Encoding always;
    try_files $uri =404;
}

Обходные приёмы с try_files $uri.br без модуля brotli приводят к неверному Content-Type, потому что Nginx определяет его по реальному расширению файла (.br). На стороне Nginx нет штатного механизма привязать text/css к .css.br, поэтому для корректной раздачи brotli используйте именно brotli_static.

HTTP/2 и HTTP/3

Сжатие — orthogonal к протоколу: будь то HTTP/1.1, HTTP/2 или HTTP/3 (QUIC), алгоритм выбора по Accept-Encoding одинаков. Главное — корректно проставлять Content-Encoding и Vary. Предсжатие особенно выгодно при HTTP/3, где латентность важна и CPU лучше потратить на TLS и обработку запросов, а не на компрессию. Для стабильной поддержки TLS и HTTP/3 пригодятся действующие SSL-сертификаты.

Apache: гибко и без плясок с бубном

В Apache предсжатая раздача особенно удобна благодаря поддержке «многочастных» расширений. Для файла style.css.br тип контента берётся из расширения .css, а .br добавляет кодек. То есть достаточно объявить, как трактовать каждый суффикс.

Минимальный набор модулей: mod_mime, mod_headers, mod_rewrite (для удобной переадресации на нужный вариант), опционально mod_brotli/mod_deflate для компрессии на лету.

Вариант с .htaccess

<IfModule mod_mime.c>
  AddType text/css .css
  AddType application/javascript .js
  AddType text/html .html
  AddType image/svg+xml .svg
  AddEncoding br .br
  AddEncoding gzip .gz
</IfModule>

<IfModule mod_headers.c>
  Header merge Vary Accept-Encoding
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  # Сначала brotli, если поддерживается
  RewriteCond %{HTTP:Accept-Encoding} br
  RewriteCond %{REQUEST_FILENAME}\.br -s
  RewriteRule ^(.+)\.(css|js|html|svg)$ $1.$2.br [QSA]
  # Затем gzip
  RewriteCond %{HTTP:Accept-Encoding} gzip
  RewriteCond %{REQUEST_FILENAME}\.gz -s
  RewriteRule ^(.+)\.(css|js|html|svg)$ $1.$2.gz [QSA]
</IfModule>

Пояснения:

  • AddEncoding br .br и AddEncoding gzip .gz обеспечивают правильный Content-Encoding.
  • AddType назначает ожидаемые MIME-типы исходным расширениям. Для предсжатого .css.br Apache автоматически возьмёт text/css по .css и добавит Content-Encoding: br по .br.
  • Header merge Vary Accept-Encoding добавит/объединит заголовок Vary, не дублируя его при многократной обработке.

Вариант в VirtualHost

<VirtualHost *:80>
  ServerName example.test
  DocumentRoot /var/www/site/public

  <IfModule mod_mime.c>
    AddType text/css .css
    AddType application/javascript .js
    AddType text/html .html
    AddType image/svg+xml .svg
    AddEncoding br .br
    AddEncoding gzip .gz
  </IfModule>

  <IfModule mod_headers.c>
    Header merge Vary Accept-Encoding
  </IfModule>

  <Directory /var/www/site/public>
    AllowOverride All
    Require all granted
  </Directory>

  <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP:Accept-Encoding} br
    RewriteCond %{REQUEST_FILENAME}\.br -s
    RewriteRule ^(.+)\.(css|js|html|svg)$ $1.$2.br [QSA]

    RewriteCond %{HTTP:Accept-Encoding} gzip
    RewriteCond %{REQUEST_FILENAME}\.gz -s
    RewriteRule ^(.+)\.(css|js|html|svg)$ $1.$2.gz [QSA]
  </IfModule>
</VirtualHost>

Такой конфиг безопасно и прозрачно раздаёт предсжатые варианты. Если для ресурса нет .br/.gz, отдаётся обычный файл.

Сравнение конфигов Nginx и Apache для предсжатой статики

Какие типы файлов компрессировать

Компрессия полезна для текстовых форматов: CSS, JS, HTML, JSON, SVG, XML, TXT, CSV, WebManifest и т.п. Для уже сжатых бинарных форматов (PNG, JPEG, WebP, MP4, PDF) выигрыша нет или он минимален, а иногда это вредно: сжатый бинарник часто становится больше из‑за overhead. В конфиге Nginx это отражается через списки gzip_types и brotli_types, в Apache — через AddType (для сопоставления MIME) и логику переписывания URI.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Генерация .br и .gz: локально и в CI/CD

Добавьте этап предсжатия в сборку. Для больших проектов это делается на уровне бандлера (Webpack, Rollup, Vite, Parcel) плагинами, для статических сайтов — отдельной утилитой. Примеры CLI:

# Brotli максимального качества (дольше, но наилучший размер)
find public -type f -regextype posix-extended -regex ".*\.(css|js|html|svg|json|xml|txt)$" -print0 | xargs -0 -I{} brotli -f -q 11 {}

# Gzip разумного компромисса
find public -type f -regextype posix-extended -regex ".*\.(css|js|html|svg|json|xml|txt)$" -print0 | xargs -0 -I{} gzip -f -k -9 {}

# Альтернатива для *.gz: zopfli (ещё меньше, но медленнее)
find public -type f -regextype posix-extended -regex ".*\.(css|js|html|svg|json|xml|txt)$" -print0 | xargs -0 -I{} zopfli --i1000 {}

Замечания:

  • Не сжимайте двоичные уже‑сжатые форматы: картинки, видео, архивы. Это лишний CPU и мусор в репозитории.
  • Убедитесь, что сборка не перезаписывает .br/.gz после минификации: сжимайте после финального шага минификации и хеширования файлов.
  • На CI полезно кешировать артефакты между билдами, чтобы не компрессировать неизменённые файлы.

Генерация .br и .gz артефактов в CI/CD пайплайне

Отладка и проверка

Проверьте заголовки и размер ответа через curl:

# Проверка brotli
curl -I -H "Accept-Encoding: br" https://example.test/assets/app.css
# Проверка gzip
curl -I -H "Accept-Encoding: gzip" https://example.test/assets/app.css
# Проверка без компрессии
curl -I -H "Accept-Encoding:" https://example.test/assets/app.css

Смотрите на Content-Encoding, Vary, Content-Length. Для предсжатых ответов размер должен совпадать с размером соответствующих .br/.gz файлов. В браузере удобны DevTools: вкладка Network, колонки Encoded/Decoded size, Response Headers.

Кэширование: Vary, ETag, Cache-Control

Для корректной работы кэшей:

  • Vary: Accept-Encoding обязателен.
  • ETag и/или Last-Modified для предсжатых вариантов будут отличаться от исходных. Это нормально: у каждого варианта свой ETag. Подробно про политику кэширования статики — в статье Cache-Control и ETag для статики.
  • Задайте Cache-Control для статики (например, длинный max-age + immutable для файлов с хешами). Это orthogonal к компрессии, но критично для производительности.
  • С CDN проверьте, что оно уважает Vary или умеет варьировать кэш по Accept-Encoding автоматически. В большинстве современных CDN это уже решено.
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Тонкости безопасности и совместимости

Предсжатие безопасно для статических ресурсов. Риски компрессионных атак (BREACH и т.п.) относятся к динамическому сжатию ответов, содержащих секреты и отражающие пользовательский ввод. Для статики это неактуально. Тем не менее:

  • Не включайте принудительный gzip_static always — это может сломать клиентов/ботов без поддержки gzip.
  • Не применяйте компрессию к архивам, двоичным файлам и медиа.
  • Следите, чтобы кросс-оригинные шрифты и важные статики отдавались с корректными типами (types в Nginx, AddType в Apache). Неверный MIME ломает поведение браузера сильнее, чем отсутствие компрессии. Рекомендую также настроить базовые заголовки безопасности: смотрите шпаргалку HTTP security headers в Nginx и Apache.

Частые ошибки

  • Забыли Vary: Accept-Encoding. Итог: кэш отдаёт gzip клиенту без поддержки.
  • Ручной try_files на $uri.br в Nginx без brotli_static. Итог: неправильный Content-Type.
  • Пересжатие уже сжатых форматов. Итог: файлы больше, CPU тратится зря.
  • Генерация .br/.gz до минификации/хеширования. Итог: компрессию надо повторять заново.
  • Неверный список gzip_types/brotli_types или AddType. Итог: часть статики не сжимается на лету, либо уходит с неверным типом.

Итоги

Предсжатие статики .br/.gz — один из самых простых и предсказуемых приёмов ускорения. В Nginx стремитесь использовать gzip_static и brotli_static — это гарантирует корректный Content-Type и минимальный overhead. В Apache достаточно объявить AddEncoding и AddType и аккуратно перенаправить запросы через mod_rewrite. Не забывайте про add_header Vary Accept-Encoding, поддерживайте актуальные списки types и встроите генерацию precompressed артефактов в сборку. Результат — мгновенная отдача и меньшая нагрузка на сервер под пиковыми нагрузками.

Поделиться статьей

Вам будет интересно

Nginx proxy_cache_path: разбор keys_zone, max_size, inactive и тонкая настройка OpenAI Статья написана AI (GPT 5)

Nginx proxy_cache_path: разбор keys_zone, max_size, inactive и тонкая настройка

Если вы кэшируете ответы через Nginx, директива proxy_cache_path определяет каталог на диске, глубину levels, объём keys_zone и пр ...
Apache mod_md и ACME: автоматизация SSL без и с cron OpenAI Статья написана AI (GPT 5)

Apache mod_md и ACME: автоматизация SSL без и с cron

Покажу, как включить модуль mod_md в Apache и настроить автоматизацию ACME с Let’s Encrypt без внешних скриптов. Разберём рабочие ...
HAProxy HTTP Cache: практическое руководство для реверс‑прокси OpenAI Статья написана AI (GPT 5)

HAProxy HTTP Cache: практическое руководство для реверс‑прокси

Разбираем встроенный HTTP cache в HAProxy: когда он уместен, как писать правила и учитывать headers, выбирать TTL, нормализовать з ...