Сжатие: gzip, brotli и zstd

Рейтинг: 57.8% · 13 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Сжатие: gzip, brotli и zstd

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd (вы здесь)
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Зачем вообще сжимать ответы

Представь типичную страницу: HTML на 80 КБ, CSS на 200 КБ, бандл JavaScript на 600 КБ, плюс JSON-ответы API. Всё это текст, а текст сжимается отлично - в 4-8 раз. Если ты отдаёшь это сырьём, клиент с мобильным интернетом тянет почти мегабайт, твой канал на аплинке прогревается, а время до первого отрисованного пикселя растёт линейно с размером. Включаешь сжатие - и те же 600 КБ JS улетают в провод как 130 КБ. Клиент получает их быстрее, ты экономишь трафик (а исходящий трафик у облачных провайдеров стоит денег), а пользователь видит страницу раньше.

Сжатие на шлюзе - это сделка: ты тратишь CPU нгинкса, чтобы сэкономить байты в сети. Сделка почти всегда выгодная, потому что сеть медленнее процессора на порядки, особенно для далёкого или мобильного клиента. Весь вопрос - как настроить эту сделку, чтобы не спалить CPU зря и не сломать кэш. Тема nginx сжатие звучит просто, но именно здесь новички теряют половину выигрыша на дефолтных значениях и кривом списке типов.

Важно сразу разделить два режима. Динамическое сжатие - nginx жмёт ответ на лету, в момент отдачи. Это работает для всего: и для статики, и для проксированного ответа от бэкенда. Предсжатие - ты заранее положил рядом с файлом его сжатую копию (style.css и style.css.gz), и nginx просто отдаёт готовый файл, не тратя CPU вообще. Предсжатие выгоднее по нагрузке, но годится только для статики, которую ты контролируешь заранее.

Изображение

nginx gzip: базовая настройка и каждая директива

gzip - встроенный модуль, есть в любой сборке nginx, Angie и OpenResty из коробки. Это твой минимум, который понимают вообще все клиенты с 2000-х годов. Базовый блок nginx gzip настройка выглядит так - именно в таком виде он лежит в эталонном проекте как globals/gzip.conf:

Код: Выделить всё

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;
Разберём по директивам, потому что каждая решает конкретную проблему.

gzip on - включает сжатие на лету. По умолчанию off. Можно включить на уровне http (глобально), а в отдельном location выключить gzip off, если там сжимать не нужно.

gzip_comp_level 5 - уровень от 1 до 9. Это главный регулятор сделки CPU против размера. Уровень 1 жмёт быстро и слабо, 9 - медленно и сильно. Ловушка в том, что зависимость нелинейная: разница в размере между 5 и 9 обычно копеечная (проценты), а разница в потраченном CPU - заметная. Поэтому эталон 5 - это золотая середина, дальше расти смысла мало. Не ставь 9 рефлекторно: на загруженном шлюзе ты сожжёшь процессор ради лишнего процента экономии. Если жмёшь статику один раз и кэшируешь - другое дело, но для динамики 5 это правильный дефолт.

gzip_min_length 1024 - не жать ответы короче 1 КБ. Причина простая: у gzip есть постоянные накладные расходы (заголовок, словарь, контрольная сумма), и крошечный ответ после сжатия может стать больше оригинала, а CPU ты потратишь зря. Берётся из заголовка Content-Length, поэтому работает только когда длина известна заранее. Для chunked-ответов без Content-Length эта проверка не сработает.

gzip_vary on - добавляет в ответ заголовок Vary: Accept-Encoding. Это критично для кэшей. Он говорит любому промежуточному кэшу (CDN, прокси, браузерный кэш): этот ответ зависит от того, какое сжатие просил клиент, храните версии раздельно. Без него ты рискуешь отдать сжатый ответ клиенту, который сжатие не понимает, или наоборот. Никогда не выключай gzip_vary, если перед тобой есть хоть какой-то кэш.

gzip_proxied any - по умолчанию nginx не жмёт ответы на проксированные запросы (когда в запросе есть заголовок Via). Логика старая: мол, раз запрос пришёл через прокси, может, тот прокси уже разберётся. На современном шлюзе это мешает - ты хочешь жать всегда, поэтому any означает "жми независимо от прокси-заголовков". Есть и тонкие значения (expired, no-cache, no-store, private, auth) если нужно жать выборочно по cache-control бэкенда, но any - честный дефолт для gateway.

gzip_types - вот где теряют выигрыш чаще всего. По умолчанию nginx жмёт ТОЛЬКО text/html и больше ничего. То есть из коробки твои CSS, JS и JSON не сжимаются вообще. Поэтому nginx gzip_types надо задавать явно и перечислять все текстовые MIME. text/html добавлять не нужно - он всегда в списке неявно. Обрати внимание на application/json (ответы API), application/javascript (бандлы), image/svg+xml (это XML-текст, жмётся прекрасно, несмотря на префикс image). А вот добавлять сюда image/png или video/mp4 - грубая ошибка, об этом ниже.

Ещё есть gzip_disable - регэксп по User-Agent, чтобы выключить gzip для сломанных клиентов. Исторически это был костыль gzip_disable "msie6" против древнего Internet Explorer 6, который криво обрабатывал сжатие. В 2026 это реликт - живых IE6 нет, и директива тебе, скорее всего, не нужна вообще. Знай, что она есть, но не копируй её из старых гайдов бездумно.

Предсжатые файлы: gzip_static

Динамика хороша, но зачем жать один и тот же style.css на каждый запрос? Директива gzip_static решает это для статики:

Код: Выделить всё

location ~* \.(css|js|svg)$ {
    gzip_static on;
    expires 30d;
    add_header Cache-Control "public";
}
С gzip_static on nginx, получив запрос на /app.js, сначала смотрит: а нет ли рядом app.js.gz? Если есть и клиент прислал Accept-Encoding с gzip - nginx отдаёт готовый .gz без единого такта CPU на сжатие. Если файла нет - падает обратно на обычную отдачу (или на динамический gzip, если он включён). Эти .gz-файлы ты генеришь на этапе сборки фронтенда, и можешь позволить себе gzip_comp_level 9 или внешний zopfli - времени на сборке не жалко, зато на проде нулевая нагрузка.

nginx brotli: сторонний модуль, но лучше gzip для текста

Brotli - алгоритм от Google, который для текста стабильно даёт на 15-25% меньший размер, чем gzip на сопоставимом уровне. Секрет - встроенный статический словарь из частых веб-фрагментов (тегов, ключевых слов), так что HTML и CSS сжимаются особенно хорошо. Все современные браузеры понимают br уже много лет.

Главное, что надо чётко зафиксировать: nginx brotli - это СТОРОННИЙ модуль ngx_brotli, его НЕТ в ванильном nginx. Это не встроенная фича, как gzip. Его нужно собрать или поставить отдельным пакетом как динамический модуль (.so) и подключить через load_module в начале конфига, до блока http:

Код: Выделить всё

load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
После этого синтаксис почти зеркалит gzip:

Код: Выделить всё

brotli on;
brotli_comp_level 5;
brotli_min_length 1024;
brotli_types
    text/plain
    text/css
    application/json
    application/javascript
    image/svg+xml;
brotli_static on;
Шкала уровней у brotli другая - от 0 до 11, а не до 9. Уровень 11 даёт максимум, но он реально медленный, его держат для предсжатия. Для динамики разумно 4-6. brotli_static on, как и gzip_static, отдаёт заранее заготовленные .br-файлы.

Образ openresty/openresty, на котором стоит эталонный проект, brotli из коробки не несёт - его нужно добавлять отдельно. Поэтому в эталоне globals/gzip.conf использует именно gzip как универсальную базу, которая работает везде без сборки модулей. Brotli - это апгрейд, который ты ставишь осознанно, когда готов собрать модуль.

nginx zstd: нативно только в Angie

Zstandard (zstd) - алгоритм от Facebook, который славится отличным балансом: высокая скорость сжатия И декомпрессии при степени сжатия на уровне gzip или лучше. Для API-gateway с большими JSON-ответами это интересный кандидат.

И вот ключевой пункт аудита, который надо запомнить намертво. Нативный nginx zstd есть ТОЛЬКО в Angie - российском форке nginx. В Angie это полноценная встроенная фича с директивами zstd on и zstd_static, оформленная так же, как gzip:

Код: Выделить всё

zstd on;
zstd_comp_level 6;
zstd_min_length 256;
zstd_types
    text/css
    application/json
    application/javascript;
zstd_static on;
В ванильном nginx нативного zstd НЕТ - там его можно получить только через сторонний модуль (zstd-nginx-module), ровно как brotli. То есть ситуация такая: gzip - встроен везде; brotli - сторонний модуль везде; zstd - встроен в Angie, сторонний в nginx. Если ты на Angie и тебе нужен zstd - это одна строчка zstd on. Если ты на nginx - готовься собирать модуль. zstd_static on по аналогии отдаёт предсжатые .zst-файлы.

Что сжимать, а что категорически нет

Сжатие имеет смысл только для данных с избыточностью - то есть для текста. Бинарные форматы, которые УЖЕ сжаты внутри себя, повторно жать бессмысленно и вредно.
  • Жать: HTML, CSS, JS, JSON, XML, обычный текст, SVG (это XML), шрифтовые форматы в текстовом виде. Всё, что человекочитаемо или структурно повторяется.
  • НЕ жать: JPEG, PNG, WebP, AVIF (уже сжаты), MP4, WebM (видео сжато кодеком), MP3, AAC, ZIP, gzip-архивы, и шрифты woff2 (внутри woff2 уже brotli).
Если ты добавишь image/jpeg в gzip_types, nginx честно прогонит картинку через gzip, потратит CPU, и на выходе получит файл того же или большего размера - в уже сжатых данных избыточности нет, жать нечего. Чистый проигрыш: и процессор сожжён, и байты не сэкономлены. Поэтому в списках типов держи только текстовые MIME и не поддавайся желанию "сжать вообще всё".

Отдельно про woff2: это распространённая ошибка - люди видят шрифт и думают "пусть жмётся". Внутри woff2 уже лежит brotli-сжатие, так что повторное gzip только мешает. Не включай его в типы.

Проверка: как убедиться, что сжатие реально работает

Не верь конфигу на слово - проверяй curl. Клиент сообщает серверу поддерживаемые алгоритмы заголовком Accept-Encoding, а сервер отвечает выбранным алгоритмом в Content-Encoding. Запрашиваем заведомо текстовый ресурс и смотрим заголовки:

Код: Выделить всё

curl -s -I -H 'Accept-Encoding: br,gzip,zstd' https://example.com/app.js
В ответе ищешь две вещи:

Код: Выделить всё

HTTP/2 200
content-type: application/javascript
content-encoding: gzip
vary: Accept-Encoding
content-encoding показывает, чем реально сжали (gzip, br или zstd). Если этой строки нет - сжатие не сработало, и надо разбираться почему: не тот MIME-тип, ответ короче min_length, или ты забыл нужный тип в gzip_types. vary: Accept-Encoding подтверждает, что gzip_vary on отработал.

Чтобы увидеть реальную экономию, сравни размеры со сжатием и без:

Код: Выделить всё

# без сжатия - сырой размер
curl -s -o /dev/null -w '%{size_download}\n' https://example.com/app.js
# со сжатием
curl -s -o /dev/null -H 'Accept-Encoding: gzip' \
     -w '%{size_download}\n' https://example.com/app.js
Второе число должно быть в разы меньше. Если они равны - сжатие не применилось.

Тонкость с приоритетом: если у тебя включены и gzip, и brotli, nginx выберет алгоритм по своей логике, а не строго по порядку в Accept-Encoding. Чтобы проверить именно brotli, проси только его: -H 'Accept-Encoding: br' и смотри content-encoding: br.

Связка с кэшем: почему Vary решает всё

Это место, где ломаются продакшены. Допустим, перед nginx стоит CDN или включён proxy_cache. Первым пришёл клиент с Accept-Encoding: gzip, получил сжатый ответ, и кэш его сохранил. Следующим пришёл старый клиент или curl без сжатия - и кэш отдаёт ему сжатый gzip-мусор, который тот не умеет распаковывать. Страница ломается.

Спасает ровно gzip_vary on (и brotli добавляет Vary сам). Заголовок Vary: Accept-Encoding заставляет кэш хранить отдельные записи под каждый вариант кодирования. Поэтому правило железное: где есть кэш - там обязателен Vary. В эталонном проекте gzip_vary on стоит глобально, и это не случайность, а защита кэш-слоя.

Типичные грабли
  • Пустой gzip_types. Самая частая. Включил gzip on, обрадовался, а жмётся только HTML. CSS и JS летят сырыми. Всегда задавай полный список типов.
  • gzip_comp_level 9 на проде. Сжёг CPU ради процента. Держи 5 для динамики.
  • Сжатие уже сжатого. image/jpeg или woff2 в типах - чистый проигрыш по CPU.
  • Нет Vary при кэше. Битые ответы клиентам, которые не поняли кодирование.
  • Думать, что brotli/zstd встроены в nginx. brotli - сторонний модуль всегда; zstd - нативен только в Angie. Ставишь load_module и собираешь модуль, иначе конфиг не стартует с ошибкой "unknown directive".
  • gzip_min_length не работает на chunked. Если бэкенд отдаёт без Content-Length, проверка длины не сработает, и крошечные ответы тоже пожмутся. Учитывай для стриминговых эндпоинтов.
  • Двойное сжатие от бэкенда. Если приложение уже отдаёт Content-Encoding: gzip, nginx не будет жать повторно - но если бэкенд жмёт, а ты ждёшь brotli от nginx, ты получишь только gzip. Решай, кто отвечает за сжатие - бэкенд или шлюз, обычно шлюз.
Мини-лаба: настрой и проверь сжатие руками

Повтори по шагам на тестовом nginx или в Docker с openresty/openresty.
  • Шаг 1. Создай тестовый файл, который заведомо стоит сжать: положи в корень сайта app.js размером хотя бы пару КБ повторяющегося текста (можно сгенерить: yes 'console.log("hello world");' | head -2000 > app.js).
  • Шаг 2. Подними nginx БЕЗ настройки gzip. Запроси curl -s -I -H 'Accept-Encoding: gzip' http://localhost/app.js - убедись, что строки content-encoding НЕТ.
  • Шаг 3. Добавь в http блок: gzip on; gzip_types application/javascript; gzip_min_length 1024; gzip_vary on;. Проверь конфиг nginx -t и перезагрузи.
  • Шаг 4. Повтори curl. Теперь в ответе должны появиться content-encoding: gzip и vary: Accept-Encoding.
  • Шаг 5. Сравни размеры через -w '%{size_download}' с заголовком Accept-Encoding: gzip и без него. Зафиксируй коэффициент.
  • Шаг 6 (если есть модуль). Подключи ngx_brotli через load_module, добавь brotli on и тот же тип. Запроси с -H 'Accept-Encoding: br' и сравни размер br против gzip - brotli должен дать меньше.
  • Шаг 7. Добавь image/jpeg в gzip_types, положи рядом jpg, запроси его со сжатием и убедись, что размер НЕ уменьшился - так ты руками увидишь, почему сжатые форматы жать нельзя.
Контрольные вопросы
  • Почему gzip_comp_level 9 редко имеет смысл на проде, и какой уровень берут эталоном?
  • Что произойдёт с кэшем, если включить gzip, но забыть gzip_vary on?
  • В чём принципиальная разница доступности zstd между ванильным nginx и Angie? А что насчёт brotli?
  • Почему добавление image/png или woff2 в gzip_types - это вредно, а не просто бесполезно?
Итог

Сжатие - дешёвый способ ускорить отдачу и срезать трафик, если настроить его головой. gzip - твоя универсальная база, встроенная везде: comp_level 5, полный список текстовых gzip_types, min_length 1024, vary on, proxied any - ровно как в эталонном globals/gzip.conf. brotli даёт лучший результат для текста, но это всегда сторонний модуль. zstd нативен только в Angie, в nginx - тоже сторонний. Жми текст, не трогай уже сжатые форматы, держи Vary при кэше и проверяй всё через curl с Accept-Encoding. Где можешь - предсжимай статику через gzip_static/brotli_static и отдавай готовые файлы с нулевой нагрузкой на CPU.
👍6 ❤️1 🔥1 😄 🤔1
Аватара пользователя
emacs5
Сообщения: 1
Зарегистрирован: 07 июн 2026, 05:36

Re: Сжатие: gzip, brotli и zstd

Сообщение emacs5 »

Спасибо, наконец дошло почему у меня js не сжимался - в gzip_types по дефолту только html, а я думал что on включает всё. Добавил типы, размер бандла упал втрое.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
sddfs
Сообщения: 2
Зарегистрирован: 17 май 2026, 12:54

Re: Сжатие: gzip, brotli и zstd

Сообщение sddfs »

А есть смысл держать и gzip и brotli одновременно? Или brotli достаточно? Боюсь что старые клиенты без br получат сырьё если оставлю только его.
👍1 ❤️1 🔥1 😄 🤔
Ответить
← Предыдущая глава
Модель доверия прокси-заголовков: realip и защита от спуфинга
Следующая глава →
Reverse proxy: proxy_pass и проброс запроса

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Сжатие в nginx: gzip и brotli

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя