1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 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, и правило чаще мешает, чем помогает.

Механизм: шесть проверок подряд

1тип ответа есть в gzip_types? 2длина больше gzip_min_length? 3в запросе нет Via - или разрешает gzip_proxied? 4клиент прислал Accept-Encoding? 5версия протокола не ниже gzip_http_version? 6на ближайшем уровне не написан gzip off? любое «нет» - и ответ уезжает как есть, молча
Ни одна из шести причин не пишет ничего в лог. Поэтому проверяют по порядку.

Правило

$  команда
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. Разобрано выше. Лечится одной строкой:

/etc/nginx/conf.d/shop.local.confhttp server
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 - размер заранее неизвестен:

↳  выводcurl -s -o /dev/null -D - -H 'Accept-Encoding: gzip' http://shop.local/data.json
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 с ним и без него.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.