# теория · шаг 2 из 5
Почему не сжимается: шесть причин
Коротко
gzip on; написан, а content-encoding не приходит. Причин ровно шесть, и
проверяются они за минуту - но одну из них почти все понимают неправильно.
- Первая и самая частая: типа нет в
gzip_types(там по умолчанию толькоtext/html). gzip_proxied- не про ответы бэкенда. Он про запросы, пришедшие с заголовкомVia, то есть через CDN или чужой прокси. Замер это показывает прямо.- Проверять надо с явным
Accept-Encoding: gzip, иначе сервер честно ответит несжатым. gzip off;на ближайшем уровне отменяет всё, что написано выше.
Если знаешь все шесть причин и правильный смысл gzip_proxied - листай до «Теперь сам».
Сначала ответь сам
gzip on, типы перечислены, за nginx стоит CDN. Статика жмётся при проверке с
сервера и не жмётся у реальных пользователей. Почему?
Потому что CDN добавляет к запросу заголовок Via, а gzip_proxied по
умолчанию off. Замер:
| запрос | ответ |
|---|---|
| обычный | 166 байт (сжат) |
тот же, но с Via: 1.1 cdn |
7637 байт (не сжат) |
статика с диска, с Via |
120391 байт (не сжат) |
Обрати внимание на третью строку: файл с диска, никакого proxy_pass. То есть
gzip_proxied не имеет отношения к тому, откуда взялся ответ, - он смотрит на
заголовок в запросе.
На что это похоже
Правило «посылки, приехавшие через посредника, не переупаковываем». Оно написано осторожно: раз кто-то уже вёз коробку, может, он её уже упаковал, а может, дальше по цепочке её ждут в определённом виде.
Логика была осмысленной во времена, когда кеширующие прокси стояли в каждой конторе. Сегодня посредник - это CDN, и правило чаще мешает, чем помогает.
Механизм: шесть проверок подряд
Правило
curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip' https://shop.local/api/cart \
| grep -iE 'content-encoding|content-type|vary'
Заголовок запроса указываем руками: без него сервер честно ответит несжатым, и
диагностика уедет не туда. Смотреть надо на content-type из ответа - именно
он сверяется с gzip_types.
Разбор: шесть причин по порядку
1. Типа нет в gzip_types. Самая частая. По умолчанию там только
text/html, поэтому из коробки не жмётся ни CSS, ни JS, ни JSON.
2. Ответ короче gzip_min_length. Умолчание 20 байт, длина берётся из
Content-Length. Замер: файл 120 КБ при gzip_min_length 200000 остался
несжатым. А вот если бэкенд отдаёт чанками без Content-Length, длина
неизвестна - и nginx сжимает, не проверяя порог.
3. В запросе есть Via. Разобрано выше. Лечится одной строкой:
gzip_proxied any;
Значение any разрешает всё. Остальные (no-cache, no-store, private,
expired, auth, no_etag, no_last_modified) разрешают сжатие только для
ответов с соответствующими заголовками - их ставят, когда часть ответов
кешируется дальше по цепочке и жать их не нужно.
4. Клиент не просил. Accept-Encoding может не дойти по двум причинам:
старый клиент (редко) или промежуточный прокси, вырезавший заголовок. Второе
встречается в корпоративных сетях и лечится только на их стороне.
5. HTTP/1.0. gzip_http_version по умолчанию 1.1. Клиенты HTTP/1.0 -
почти исключительно внутренние скрипты и старые балансировщики.
6. Ты смотришь не туда. gzip off; в конкретном location отменяет всё,
что написано выше: правило наследования из второго раздела работает и здесь.
nginx -T | grep -n gzip
Покажет все места разом, включая подключённые файлы.
Разбор: чего не покажет проверка
Сжатый ответ едет с Transfer-Encoding: chunked вместо Content-Length -
размер заранее неизвестен:
Transfer-Encoding: chunked
Content-Encoding: gzip
Отсюда практический приём: если хочется увидеть выигрыш в байтах, смотри не на заголовки, а на реально скачанный объём:
curl -s -o /dev/null -w '%{size_download}\n' -H 'Accept-Encoding: gzip' https://shop.local/data.json
curl -s -o /dev/null -w '%{size_download}\n' https://shop.local/data.json
Два числа рядом отвечают на вопрос лучше любого заголовка.
Что ломается без этого
Двойное сжатие. Если приложение отдаёт Content-Encoding: gzip само, nginx
повторно жать не будет. Хуже другое: при proxy_set_header Accept-Encoding ""
(так иногда отключают сжатие на бэкенде ради sub_filter) бэкенд перестаёт
сжимать, и весь трафик до nginx идёт открытым текстом. На внутренней сети это
обычно нормально, но цену стоит помнить.
BREACH. Про эту атаку нужно знать одну практическую вещь: если в одном ответе одновременно есть секрет (CSRF-токен) и данные, которыми управляет злоумышленник, сжатие теоретически позволяет секрет угадать по изменению размера. Отсюда рекомендация не сжимать ответы с секретами - на практике это про динамический HTML с токенами, а не про статику. Полностью отключать gzip современные руководства не советуют.
Зачем это в работе
Разбор «почему не жмётся» занимает минуту, если идти по списку, и полдня, если
менять настройки наугад. Порядок такой: сначала curl с явным
Accept-Encoding, потом content-type против gzip_types, потом nginx -T |
grep gzip на предмет gzip off уровнем ниже, и только потом всё остальное.
И отдельно проверь этот же адрес через CDN, а не только с сервера: третья причина проявляется только там.
Теперь сам
curl с сервера показывает content-encoding: gzip, а вкладка Network в
браузере - несжатый ответ того же адреса. Обе проверки честные. Где разница?
Скорее всего, браузер ходит через CDN или корпоративный прокси, который
добавляет Via, - и на такой запрос сжатие выключено умолчанием
gzip_proxied off. Проверяется добавлением заголовка к своему же curl:
-H 'Via: 1.1 test'. Если размер ответа подскочил - причина найдена.
Главное
Шесть причин, по которым nginx откажется сжимать: типа нет в gzip_types (там
по умолчанию только text/html), ответ короче gzip_min_length, в запросе есть
Via при умолчании gzip_proxied off, клиент не прислал Accept-Encoding,
протокол ниже gzip_http_version и gzip off на ближайшем уровне. Главное
заблуждение - считать gzip_proxied настройкой про ответы бэкенда: замер
показывает, что он смотрит на заголовок Via в запросе и выключает сжатие даже
для файла с диска. Диагностика - один curl с явным Accept-Encoding и
сравнение size_download с ним и без него.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий