Сжатие HTTP-ответов — одна из тех оптимизаций, которая выглядит простой до первой ошибки в продакшене. Включили gzip on, добавили пару MIME-типов, проверили главную страницу — и вроде бы всё хорошо. Но затем выясняется, что API начал отдавать странные заголовки, CDN кэширует Brotli-версию для клиентов без Brotli, статика сжимается повторно, а CPU на небольшом сервере внезапно стал узким местом.
В этой инструкции разберём практическую схему для Nginx и Apache: как включить gzip и Brotli, какие типы файлов сжимать, как избежать double compression, зачем нужен Vary: Accept-Encoding и как безопасно кэшировать сжатые ответы. Тема особенно полезна для администраторов, которые держат сайты, API, WordPress, Laravel, SPA или статические фронтенды на одном сервере и хотят получить ускорение без побочных эффектов.
Что именно даёт gzip и Brotli
gzip — старый, надёжный и почти везде поддерживаемый алгоритм сжатия. Он хорошо работает для HTML, CSS, JavaScript, JSON, XML, SVG и текстовых ответов API. Brotli обычно даёт более компактный результат на статических файлах, особенно на CSS и JavaScript, но требует поддержки со стороны браузера и модуля на сервере.
Важно понимать: сжатие уменьшает размер ответа, но не делает сервер магически быстрее во всех сценариях. Если у вас тяжёлые SQL-запросы, медленный PHP-код или очередь на диск, gzip и Brotli не исправят TTFB. Они помогают после того, как ответ уже сформирован: меньше байт уходит по сети, быстрее загружается фронтенд, ниже расход трафика.
Хорошее правило: динамику сжимайте умеренно на лету, а крупные неизменяемые static assets лучше готовьте заранее в виде
.gzи.br.
Для динамических страниц обычно достаточно gzip уровня 4–6 или Brotli качества 4–5. Максимальные уровни сжатия редко оправданы: экономия в процентах небольшая, а нагрузка на CPU растёт заметно. Для заранее подготовленной статики можно позволить себе более высокий уровень, потому что сжатие выполняется на этапе сборки или деплоя, а не на каждом запросе.
Что сжимать, а что лучше не трогать
Сжимать стоит только данные, в которых есть повторяемость. HTML, CSS, JS, JSON, XML, SVG, текстовые шрифтовые манифесты и WASM обычно выигрывают. Уже сжатые форматы — JPEG, PNG, WebP, AVIF, MP4, PDF, ZIP, GZ, BR, 7z — повторно сжимать не нужно: вы почти ничего не выиграете, зато потратите CPU и иногда получите файл чуть больше исходного.
Отдельно стоит относиться к маленьким ответам. Если тело меньше 512–1024 байт, выигрыш от сжатия часто съедается заголовками и накладными расходами. Поэтому в Nginx полезен gzip_min_length и brotli_min_length, а в Apache — аккуратная фильтрация по MIME-типам и URI.
- Сжимайте:
text/html,text/css,application/javascript,application/json,application/xml,image/svg+xml,application/wasm. - Не сжимайте: изображения, видео, архивы, уже подготовленные
.gzи.br, большие бинарные выгрузки. - Проверяйте отдельно: потоковые ответы, Server-Sent Events, большие download-эндпоинты и ответы, которые проксируются от стороннего backend.
SEO-формулировка nginx gzip brotli часто встречается в чек-листах производительности, но на практике важнее не просто включить обе технологии, а настроить их так, чтобы они не конфликтовали с кэшем, CDN и приложением.

Базовая настройка gzip и Brotli в Nginx
В Nginx gzip обычно доступен из коробки. Brotli чаще поставляется отдельным динамическим модулем. Перед настройкой проверьте, какие модули есть в вашей сборке:
nginx -V 2>&1 | grep -E 'gzip|brotli'
nginx -t
Если Brotli-модуля нет, варианты зависят от репозитория и сборки Nginx. На Debian/Ubuntu модуль часто доступен отдельными пакетами. В RHEL-based системах пакет может быть в EPEL или в репозитории поставщика Nginx. Если готового пакета нет, не спешите пересобирать продакшен-сервер: сначала оцените, действительно ли Brotli нужен именно на origin, а не на CDN или в pipeline сборки.
Debian/Ubuntu
sudo apt update
sudo apt install nginx
sudo apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static
sudo nginx -t
sudo systemctl reload nginx
AlmaLinux, Rocky Linux, CentOS Stream, Oracle Linux, Fedora
sudo dnf install nginx
sudo dnf install nginx-mod-http-brotli
sudo nginx -t
sudo systemctl reload nginx
Если пакет nginx-mod-http-brotli не найден, проверьте подключённые репозитории. Для production-сервера лучше использовать один понятный источник пакетов, чем смешивать несколько несовместимых сборок Nginx и модулей.
Пример настройки в контексте http. Обычно его размещают в /etc/nginx/nginx.conf или в отдельном файле, подключённом внутри http:
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_proxied any;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml application/rss+xml image/svg+xml application/wasm font/ttf font/otf application/vnd.ms-fontobject;
brotli on;
brotli_comp_level 5;
brotli_min_length 1024;
brotli_types text/plain text/css text/xml application/json application/javascript application/xml application/rss+xml image/svg+xml application/wasm font/ttf font/otf application/vnd.ms-fontobject;
brotli_static on;
gzip_static on;
Директива gzip_vary on добавляет заголовок Vary: Accept-Encoding. Это критично для прокси и кэшей: один и тот же URL может иметь разные варианты ответа — без сжатия, gzip и br. Если промежуточный кэш не понимает, что варианты разные, он может отдать клиенту неподдерживаемый формат.
Директивы brotli_static on и gzip_static on говорят Nginx: если рядом с файлом лежит заранее сжатая версия .br или .gz, отдавай её подходящему клиенту. Для static assets это часто лучше, чем сжимать на лету при каждом запросе.
Apache compression: mod_deflate и mod_brotli
В Apache gzip обычно настраивается через mod_deflate, Brotli — через mod_brotli. Подход похожий: включаем модули, задаём MIME-типы, добавляем Vary, исключаем уже сжатые и бинарные файлы.
Debian/Ubuntu
sudo apt update
sudo apt install apache2
sudo a2enmod deflate headers filter brotli
sudo apache2ctl configtest
sudo systemctl reload apache2
AlmaLinux, Rocky Linux, CentOS Stream, Oracle Linux, Fedora
sudo dnf install httpd
sudo httpd -M | grep -E 'deflate|brotli|headers'
sudo apachectl configtest
sudo systemctl reload httpd
Если mod_brotli не загружен, проверьте файл с модулями в /etc/httpd/conf.modules.d/ или наличие отдельного пакета в вашем репозитории. На части систем Brotli уже входит в сборку Apache, но не активирован.
Пример конфигурации для виртуального хоста или общего файла, подключённого в Apache:
<IfModule mod_headers.c>
Header merge Vary Accept-Encoding
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml application/json application/javascript application/xml image/svg+xml application/wasm
DeflateCompressionLevel 5
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif|mp4|zip|gz|br|bz2|xz|7z)$ no-gzip
</IfModule>
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css text/xml application/json application/javascript application/xml image/svg+xml application/wasm
BrotliCompressionQuality 5
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif|mp4|zip|gz|br|bz2|xz|7z)$ no-brotli
</IfModule>
В коде выше HTML-теги Apache показаны как текст, потому что это пример конфигурации. В реальном файле они должны быть обычными директивами Apache. После изменения всегда выполняйте apachectl configtest или apache2ctl configtest, а затем делайте reload, не restart, если нет необходимости прерывать соединения.
Как избежать double compression
double compression — это ситуация, когда ответ уже сжат backend-приложением, а reverse proxy пытается сжать его ещё раз. В лучшем случае вы потратите CPU впустую. В худшем — получите некорректные заголовки, проблемы с кэшем или несовместимость с клиентами.
Типичный сценарий: приложение Node.js, PHP-фреймворк или upstream Apache уже отдаёт Content-Encoding: gzip, а перед ним стоит Nginx с включённым gzip/Brotli. Чтобы контролировать сжатие в одном месте, часто удобнее запретить upstream сжимать ответы и поручить это внешнему Nginx:
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Accept-Encoding "";
}
Такой приём удаляет Accept-Encoding из запроса к backend, и приложение обычно возвращает несжатое тело. Внешний Nginx затем применяет gzip или Brotli один раз, добавляет корректный Content-Encoding и Vary.
Если у вас наоборот Apache является фронтом перед приложением, логика та же: решите, где именно выполняется сжатие. Не включайте компрессию одновременно в приложении, Apache, Nginx и CDN без чёткого понимания цепочки. Один уровень должен быть ответственным за финальный HTTP-ответ клиенту.
Проверить двойное сжатие можно по заголовкам и фактическому телу ответа. Если в цепочке появляются два разных слоя, часто видно лишние преобразования, странные размеры, ошибки распаковки или отсутствие ожидаемого Content-Length. Для динамических ответов нормально, что при сжатии используется chunked transfer; ненормально, когда клиент не может распаковать ответ.
Vary: Accept-Encoding и кэш сжатых ответов
Заголовок Vary: Accept-Encoding сообщает кэшу: версия ответа зависит от того, какие кодировки клиент указал в Accept-Encoding. Это основа безопасной работы gzip/Brotli с browser cache, CDN, Nginx proxy cache, Apache mod_cache и другими промежуточными слоями.
Без Vary кэш может сохранить Brotli-ответ и отдать его клиенту, который поддерживает только gzip или вообще не поддерживает сжатие. В браузере это проявляется как ошибка загрузки ресурса, битый CSS/JS или непонятный текст вместо страницы. Поэтому фраза vary accept-encoding — не просто SEO-ключ, а рабочий чек-лист для любой настройки compression.
Если вы используете Nginx как reverse proxy cache и хотите cache compressed responses, не подставляйте сырой $http_accept_encoding в ключ кэша без нормализации. В этом заголовке может быть много вариантов записи, из-за чего кэш начнёт дробиться на десятки почти одинаковых ключей. Лучше привести его к нескольким состояниям: br, gzip или identity.
map $http_accept_encoding $cache_encoding {
default identity;
~*br br;
~*gzip gzip;
}
proxy_cache_key $scheme$request_method$host$request_uri$cache_encoding;
Этот пример размещается в контексте http. Он упрощает ключ кэша и снижает риск, что один и тот же URL будет храниться в сотнях вариантов из-за разных строк Accept-Encoding. При этом порядок важен: если клиент поддерживает Brotli, обычно выбираем br; если нет, но поддерживает gzip — gzip; иначе identity.
Для Apache mod_cache обычно достаточно корректного Vary, но всё равно проверяйте поведение конкретной связки. Особенно внимательно тестируйте конфигурации, где Apache стоит за Nginx, перед CDN или рядом с application cache. Проблемы возникают не из-за самого gzip, а из-за того, что несколько слоёв по-разному понимают вариантность ответа.
Предварительное сжатие static assets
Для фронтенда с хешированными именами файлов — например app.9f3a1c.js и style.4b22.css — выгодно готовить сжатые версии при деплое. Тогда веб-сервер просто отдаёт готовый .br или .gz, не тратя CPU на каждый запрос. Это особенно заметно на небольших VDS, где один процесс сборки можно пережить спокойно, а постоянное сжатие в пике уже влияет на latency.
Установите утилиту Brotli и подготовьте файлы. Пример ниже подходит для простой директории assets; в реальном CI/CD лучше встроить это в pipeline сборки:
Debian/Ubuntu
sudo apt update
sudo apt install brotli gzip
AlmaLinux, Rocky Linux, CentOS Stream, Oracle Linux, Fedora
sudo dnf install brotli gzip
for f in /var/www/site/public/assets/*.{css,js,svg,json,wasm}; do [ -f "$f" ] && gzip -k -f -9 "$f"; done
for f in /var/www/site/public/assets/*.{css,js,svg,json,wasm}; do [ -f "$f" ] && brotli -f -q 11 "$f"; done
Уровень brotli -q 11 хорош именно для предварительного сжатия, но не для динамики на лету: он слишком дорогой по CPU. Для runtime-компрессии используйте умеренные уровни. Если assets имеют хеш в имени и меняются только при новой сборке, задайте долгий кэш браузера и CDN. Подробнее о связке immutable-файлов, Cache-Control и валидаторов можно посмотреть в материале про Cache-Control, ETag и кэширование статики.
Для заранее сжатых файлов проверьте, что сервер отдаёт правильный исходный MIME-тип, а не application/octet-stream для .br. Клиент должен видеть, например, Content-Type: text/css и Content-Encoding: br, а не думать, что скачивает самостоятельный Brotli-файл.

Проверка через curl и браузер
После настройки не ограничивайтесь визуальной проверкой сайта. Нужны запросы с разными Accept-Encoding. Так вы увидите, что сервер отдаёт br, gzip и несжатый вариант корректно.
curl -I -H 'Accept-Encoding: br' https://example.test/assets/app.css
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: br, во втором — Content-Encoding: gzip, в третьем заголовка Content-Encoding быть не должно. Во всех вариантах, где ресурс может отличаться по кодировке, нужен Vary: Accept-Encoding.
Для динамической страницы проверьте главную, страницу с большим HTML, JSON API и ошибку 404. Иногда администраторы настраивают compression только для основного location, а error pages, API или proxy location остаются без нужных заголовков. В DevTools браузера смотрите не только размер transfer, но и response headers.
curl -s -H 'Accept-Encoding: br' https://example.test/ -o /tmp/page.br
file /tmp/page.br
curl -s -H 'Accept-Encoding: gzip' https://example.test/ -o /tmp/page.gz
file /tmp/page.gz
Если файл определяется как сжатый поток — это нормально для такой проверки. Если браузер ругается на распаковку, проверьте, не вмешивается ли application middleware, WAF, CDN, upstream proxy или старый rewrite, который подменяет заголовки.
Типовые ошибки в продакшене
Первая ошибка — включить максимальные уровни сжатия на динамике. На небольшом сервере это быстро превращается в лишнюю нагрузку, особенно если у сайта много HTML-страниц без кэша. Лучше начать с уровня 5, посмотреть CPU, latency и размер ответов, а затем точечно менять настройки.
Вторая ошибка — забыть про Vary: Accept-Encoding. Это особенно опасно, если между клиентом и origin есть CDN или Nginx proxy cache. Симптомы могут быть плавающими: у одного пользователя всё работает, у другого не грузится JS, у третьего проблема исчезает после очистки кэша.
Третья ошибка — сжимать всё подряд. Изображения, архивы и видео почти не уменьшаются, но CPU расходуют. Для больших скачиваний компрессия может даже ухудшить поведение: вместо быстрой отдачи файла сервер начинает долго обрабатывать поток.
Четвёртая ошибка — не учитывать upstream. Если backend уже отдаёт Content-Encoding, фронтовый сервер должен это уважать или явно забирать у backend несжатый ответ. Иначе появляется та самая double compression, которую сложно заметить на глаз, но легко поймать тестами с curl.
Пятая ошибка — смешивать предварительно сжатые файлы и динамическое сжатие без порядка. Если рядом лежат .br и .gz, сервер должен корректно выбрать лучший вариант по Accept-Encoding. Если заранее сжатого файла нет, можно использовать runtime-сжатие. Но не нужно пересжимать уже готовый .br.
Практичный пресет для большинства сайтов
Для обычного сайта или небольшого приложения можно начать с такой политики: gzip включён всегда для текстовых MIME-типов; Brotli включён, если модуль доступен без нестабильной сборки; уровни сжатия 4–6; для static assets в CI/CD готовятся .br и .gz; кэш учитывает Accept-Encoding; upstream-приложения не сжимают ответы самостоятельно, если это уже делает фронтовый сервер.
Если сайт в основном статический, вкладывайтесь в предварительное сжатие и долгий cache-control для хешированных файлов. Если сайт динамический, сначала включите умеренный gzip, затем проверьте нагрузку и только потом добавляйте Brotli. Если перед origin стоит CDN, уточните, где именно будет выполняться сжатие: иногда лучше оставить origin проще, а Brotli доверить edge-уровню.
Итоговая цель — не включить все галочки, а получить предсказуемую цепочку: клиент сообщает Accept-Encoding, сервер выбирает один подходящий вариант, кэш хранит его отдельно, заголовки честно описывают тело ответа, а CPU не тратится на бесполезную работу. Тогда apache compression и nginx gzip brotli действительно ускоряют сайт, а не добавляют ещё один источник редких и неприятных инцидентов.


