gzip, Brotli, Zstd HTTP сжатие для разработчиков, которые случайно устанавливают content-encoding: identity
Как работает согласование сжатия HTTP (Accept-Encoding / Content-Encoding), сравнительный анализ gzip, Brotli и Zstd, как проверить, что сжатие действительно включено с помощью curl, и четыре неправильной настройки, которые молча отключают его.
Ваша конфигурация nginx имеет gzip on;. Ваше приложение возвращает JSON. Тело ответа всё ещё несжато и составляет 35 КБ. Нет ошибок, нет предупреждений — сжатие просто не происходит молча.
Это обычно одна из четырёх ошибок настройки. Но сначала: как работает переговоры.
Как работает переговоры по сжатию HTTP
Два заголовка, ничего больше. Клиент объявляет, какие типы сжатия он может декодировать в Accept-Encoding. Сервер выбирает алгоритм, сжимает тело и объявляет свой выбор в Content-Encoding:
GET /api/data HTTP/1.1
Host: example.com
Accept-Encoding: gzip, deflate, br, zstd
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: br
Vary: Accept-Encoding
The Vary: Accept-Encoding заголовок является необязательным, если вы хотите, чтобы CDN корректно кэшировал ответы. Без него CDN может кэшировать сжатый ответ Brotli и подавать его клиенту, который только объявил gzip в Accept-Encoding. Тот клиент затем пытается декодировать Brotli как gzip и получает мусор. nginx добавляет это автоматически. gzip_vary on; является технически корректным — оно означает «без сжатия» — но никто не устанавливает его явно. Реальный сценарий сбоя — наоборот: отсутствие заголовка
Content-Encoding: identity всех, когда вы ожидаете его присутствия. Content-Encoding заголовок в любом случае, когда вы ожидали его.
Проверка того, что сжатие действительно работает
Перед тем как отлаживать конфигурацию, убедитесь в том, что проблема:
# Check headers only
curl -sI -H "Accept-Encoding: gzip, br, zstd" https://example.com/api/data | grep -i "content-encoding\|vary"
# Compare compressed vs uncompressed size
curl -so /dev/null -w "uncompressed: %{size_download} bytes
" https://example.com/api/data
curl -so /dev/null --compressed -w "compressed: %{size_download} bytes
" https://example.com/api/data
--compressed отправляется Accept-Encoding: deflate, gzip, br, zstd автоматически и декодируется. Если оба значения совпадают, сжатие не включается. Если вы хотите проверить все заголовки ответа и то, что они означают в контексте, то Анализатор HTTP-заголовков добавит аннотации, включая Vary, Content-Encodingи директивы cache-control.
gzip против Brotli против Zstd
Три алгоритма практически значимы для HTTP сегодня. Ниже приведены данные из официальных тестов Zstd на корпусе Silesia — стандартном наборе смешанных реальных файлов (HTML, исходный код, PDF, базы данных), протестированных на процессоре Core i7-9700K. Платформы с чистым JSON или простым текстом обычно сжимаются лучше.
| Алгоритм | Уровень | Отношение | Компресс | Распаковать |
|---|---|---|---|---|
| gzip | 1 (быстрый) | 2.74x | 69 МБ/с | 380 МБ/с |
| gzip | 6 (по умолчанию) | 2.97x | 29.9 МБ/с | 360 МБ/с |
| gzip | 9 (максимум) | 3.10x | 18 МБ/с | 360 МБ/с |
| Brotli | 4 | 3.18x | 104 МБ/с | 440 МБ/с |
| Brotli | 11 (максимум) | 3.74x | 0.4 МБ/с | 440 МБ/с |
| Zstd | 1 (быстрый) | 2.88x | 430 МБ/с | 1380 МБ/с |
| Zstd | 3 (по умолчанию) | 3.01x | 320 МБ/с | 1350 МБ/с |
| Zstd | 19 (максимум) | 3.40x | 17.5 МБ/с | 1380 МБ/с |
gzip является базовым. Уровень 6 — правильный выбор для мгновенного сжатия — затраты на 651 ТП7Т больше CPU при переходе от уровня 6 к уровню 9 дают прирост в 41 ТП7Т, что не оправдано для динамических ответов. Для статических файлов, предварительно сжатых, это другое вычисление.
Brotli на самом деле превосходит gzip при сопоставимых затратах CPU на уровнях 4–6 и декодируется примерно на 201 ТП7Т быстрее. Причина: Brotli имеет статический словарь, настроенный под веб-контент — HTML-сущности, имена полей HTTP, ключевые слова JavaScript. Он даёт лучшие соотношения по сравнению с универсальным компрессором на одном и том же материале. Уровень 11 применим только для предварительно сжатых статических ресурсов; при скорости сжатия 0,4 МБ/с вы сжимаете около 25 МБ в минуту. Это этап сборки, а не обработки запроса.
Zstd является сценарием скорости. Уровень по умолчанию (3) даёт соотношение, равное gzip, но сжимает в 10 раз быстрее и декодирует почти в 4 раза быстрее. Основное ограничение — поддержка браузеров: Chrome 118+ (октябрь 2023), Firefox 126+ (май 2024), Safari 18+ (конец 2024). Ещё не достаточно универсально, чтобы использовать как единственный алгоритм, но если ваш сервер правильно переговаривает, добавление Zstd требует лишь нескольких строк конфигурации и помогает клиентам, которые его объявляют. Уровень Zstd 19 приближается к соотношению Brotli-11 без катастрофической потери скорости сжатия, что делает его более применимым для мгновенного сжатия при высоких требованиях к сжатию.
Поддержка браузеров и клиентов
| Алгоритм | Chrome | Firefox | Safari | Edge | Node.js |
|---|---|---|---|---|---|
| gzip | Все | Все | Все | Все | Встроенный (zlib) |
| deflate | Все | Все | Все | Все | Встроенный (zlib) |
| Brotli (br) | 51+ | 44+ | 11+ | 15+ | v10.16+ |
| Zstd | 118+ | 126+ | 18+ | 118+ | v21+ |
Одно важное особенность: br и zstd появляются только в Accept-Encoding при HTTPS-соединениях. Браузеры намеренно не объявляют их при обычных HTTP-соединениях — это защита от атак MITM, которые могут вставить заголовки сжатия. Если вы тестируете на http://localhost и спрашиваете, почему вы видите только gzip, deflate, это именно так. Тестируйте через HTTPS или используйте curl напрямую (curl не применяет это ограничение).
Четыре настройки, которые молча разрушают его
1. отсутствует gzip_proxied (nginx как обратный прокси)
модуль nginx gzip сжимает ответы, которые он сам генерирует. Для прокси-запросов (приложение upstream до nginx до клиента) вам нужно gzip_proxied — иначе nginx сжимает только ответы от своих собственных обработчиков, а не от proxy_pass upstreams.
# This is NOT enough when nginx is a reverse proxy:
gzip on;
gzip_types text/plain application/json application/javascript text/css;
# You need this too:
gzip_proxied any;
Большинство настроек nginx — это обратные прокси. Большинство руководств не упоминают gzip_proxied. Эти два факта объясняют много молча несжатых ответов.
2. MIME-тип не указан в gzip_types
по умолчанию nginx gzip_types является text/html только. JSON, CSS, JavaScript, SVG — все несжаты, если не указано явно:
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/rss+xml
image/svg+xml;
nginx проверяет базовый MIME-тип, поэтому application/json охватывает application/json; charset=utf-8. Не нужно указывать варианты кодировки отдельно.
3. Промежуточный прокси удаляет Accept-Encoding
AWS ALB, неправильно настроенные Cloudflare Workers и некоторые настройки API-гейтвей удаляют или изменяют Accept-Encoding до того, как он достигнет вашего источника. Сервер никогда не видит этот заголовок, и по умолчанию не включает сжатие, и все, что идёт далее, думают, что функция сломана, когда на самом деле проблема — в промежуточном слое. В цепочке не появляется никакой ошибки.
Отладка путём сравнения ответа источника и ответа CDN:
# Via CDN/proxy
curl -sI -H "Accept-Encoding: gzip, br" https://example.com/api/data
# Direct to origin (bypassing CDN via --resolve or direct IP)
curl -sI -H "Accept-Encoding: gzip, br" --resolve "example.com:443:ORIGIN_IP" https://example.com/api/data
Если источник возвращает Content-Encoding: gzip прямым образом, а ответ CDN не имеет Content-Encoding, то CDN удаляет что-то — скорее всего удаляет Accept-Encoding на входе, поэтому источник никогда не сжимает вовсе.
4. Приложение upstream сжимает ответ, затем nginx пытается сжать снова
Если ваше приложение на Node.js/Go/Python сжимает тело ответа и устанавливает Content-Encoding: gzip, nginx должен пропустить двойное сжатие — но это зависит от времени заголовков. Если upstream отправляет заголовок в процессе передачи или если обнаружение nginx соревнуется, вы можете получить двойно сжатый мусор, который клиенты не могут декодировать.
Чистое решение: пусть nginx будет отвечать за всё сжатие. Удалите компрессионные модули из вашего приложения (например, модуль express’s compression , Go’s gzip.Handlerи т.д.), возвращайте чистые ответы и пусть nginx сжимает на краю. Тот же уровень производительности, без риска двойного сжатия.
Работающие конфигурации
nginx
gzip on;
gzip_vary on; # adds Vary: Accept-Encoding automatically
gzip_proxied any; # compress responses from proxied upstreams
gzip_comp_level 6;
gzip_min_length 256; # skip tiny responses where overhead isn't worth it
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml
application/rss+xml
image/svg+xml;
# Brotli requires the ngx_brotli module
# https://github.com/google/ngx_brotli
brotli on;
brotli_comp_level 4;
brotli_static on; # serve pre-compressed .br files when they exist
brotli_types
text/plain
text/css
application/json
application/javascript
image/svg+xml;
Apache
LoadModule deflate_module modules/mod_deflate.so
AddOutputFilterByType DEFLATE text/html text/plain text/css application/json application/javascript image/svg+xml
Header append Vary Accept-Encoding
# mod_brotli requires Apache 2.4.26+
LoadModule brotli_module modules/mod_brotli.so
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css application/json application/javascript image/svg+xml
Caddy
Caddy включает gzip и Brotli по умолчанию. Чтобы явно добавить Zstd:
example.com {
encode gzip zstd br
reverse_proxy localhost:3000
}
Нет списков MIME-типов, нет gzip_proxied особенных случаев, корректная Vary обработка встроена. Честный ответ на вопрос «какой сервер имеет наименьшую поверхность ошибок, связанных с сжатием» — это Caddy.
Тестирование сжатия на собственных пакетах
Данные по корпусу Silesia показывают относительную производительность, но ваш конкретный пакет важнее. Повторяющийся ответ API с постоянными именами полей сжимается иначе, чем минифицированный JavaScript или смешанный HTML. Эти инструменты позволяют тестировать конкретные пакеты прямо в браузере без запуска локального сервера сжатия:
- Тестер Gzip / Zlib / Deflate — вставьте свой пакет, увидите сжатый размер и соотношение сразу
- Кодировщик/декодировщик сжатия Brotli — протестируйте сжатие Brotli на разных уровнях качества
- Инструмент сжатия Zstandard (Zstd) — Zstd сжимает и декодирует в браузере
Полезно при принятии решения о предварительном сжатии статических ресурсов на уровне Brotli-11 или о том, чтобы просто позволить nginx сжимать на лету с gzip. Вставьте ваш реальный ответ, сравните соотношения и сделайте выбор на основе реальных данных.
Подводя итог
Если ответы не сжимаются и curl -sI подтверждает отсутствие Content-Encoding, то решение почти наверняка одно из четырёх вышеуказанных настроек — наиболее вероятно gzip_proxied any; для nginx или CDN, который удаляет ваш Accept-Encoding заголовок. Проверьте источник напрямую, прежде чем обвинять конфигурацию сервера.
Что касается выбора алгоритма: gzip-6 подходит для динамических API-ответов и несёт почти нулевой риск настройки. Добавьте Brotli для статических ресурсов — предварительно сжимайте на уровне 11 в процессе сборки, подавайте с brotli_static on, и пусть nginx будет падать на gzip для клиентов, которые не объявляют br. Zstd стоит добавить сейчас; стоимость настройки минимальна, и его влияние на браузеры растёт. Предложение всех трёх алгоритмов с правильной Vary: Accept-Encoding обработкой — правильная позиция для всего нового.
Вам также может понравиться
Установите наши расширения
Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска
恵 Табло результатов прибыло!
Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!
Подписаться на новости
все Новые поступления
всеОбновлять: Наш последний инструмент было добавлено 15 Июня 2026
