dynamic-etag: модуль NGINX для добавления ETag к динамическому контенту
Установка
Вы можете установить этот модуль в любом дистрибутиве на основе RHEL, включая, но не ограничиваясь:
- RedHat Enterprise Linux 7, 8, 9 и 10
- CentOS 7, 8, 9
- AlmaLinux 8, 9
- Rocky Linux 8, 9
- Amazon Linux 2 и Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-dynamic-etag
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-dynamic-etag
Включите модуль, добавив следующее в начало /etc/nginx/nginx.conf:
load_module modules/ngx_http_dynamic_etag_module.so;
Этот документ описывает nginx-module-dynamic-etag v0.3.0, выпущенный 14 ноября 2026 г.
ETag для ответов, которые ваше приложение генерирует на лету.
NGINX добавляет ETag к статическим файлам и ничего — к тому, что приходит от PHP-FPM,
проксируемого приложения или любого другого upstream. Такие страницы затем скачиваются
полностью при каждом посещении, даже если не изменилось ни байта. Этот модуль хеширует
тело ответа, отправляет хеш в качестве ETag и отвечает на совпадающий
If-None-Match кодом 304 Not Modified без тела.
Он работает со всеми модулями upstream: proxy_pass, fastcgi_pass,
uwsgi_pass, scgi_pass, grpc_pass.
Краткий обзор
location ~ \.php$ {
dynamic_etag on;
fastcgi_pass unix:/run/php-fpm/www.sock;
include fastcgi_params;
}
$ curl -sI -X GET https://example.com/ | grep -i etag
ETag: "6f5902ac237024bdd0c176cb93063dc4"
$ curl -s -o /dev/null -w '%{http_code} %{size_download}\n' \
-H 'If-None-Match: "6f5902ac237024bdd0c176cb93063dc4"' https://example.com/
304 0
Как это работает
ETag должен быть в заголовке ответа, а заголовок отправляется раньше тела.
Поэтому модуль придерживает ответ: он собирает тело по мере того, как его доставляет
upstream, хеширует его целиком (MD5) и только затем отправляет заголовок,
теперь уже с ETag, а следом — тело.
- Хеш всегда покрывает всё тело, как бы оно ни приходило: одним куском
или множеством, с включённым или выключенным
proxy_buffering, из памяти или из временного файла, в который NGINX его сбросил. - Тело, превышающее
dynamic_etag_buffer_size, не получаетETagи передаётся без изменений. Частичного хеша не бывает. - Условные запросы обрабатываются самим NGINX на основе вычисленного
ETag: наIf-None-Matchвозвращается304, на неудачныйIf-Match—412. - Тело хешируется до сжатия. Затем
gzipи другие фильтры сжатия ослабляютETag(W/"..."), как и для статических файлов.
Директивы
dynamic_etag
- синтаксис:
dynamic_etag on | off | $variable - по умолчанию:
off - контекст:
http,server,location
Включает ETag для ответов upstream. При использовании переменной ETag добавляется,
когда её значение равно on; см. Условное использование.
dynamic_etag_types
- синтаксис:
dynamic_etag_types mime-type ... - по умолчанию:
text/html - контекст:
http,server,location
Добавляет ETag к ответам с перечисленными MIME-типами в дополнение к
text/html. Специальное значение * соответствует любому типу.
dynamic_etag_strength
- синтаксис:
dynamic_etag_strength strong | weak | $variable - по умолчанию:
strong - контекст:
http,server,location
Делает сгенерированный ETag сильным ("...") или слабым (W/"..."). Слабый ETag
означает, что страница эквивалентна, а не побайтово идентична. При использовании
переменной ETag становится слабым, когда её значение равно weak.
dynamic_etag_buffer_size
- синтаксис:
dynamic_etag_buffer_size size - по умолчанию:
1m - контекст:
http,server,location
Наибольший размер тела ответа, который модуль удерживает в памяти для хеширования. Ответ
большего размера отправляется без ETag, в точности так, как NGINX отправил бы его
с выключенным модулем.
Какие ответы получают ETag
Все перечисленные условия должны выполняться:
- ответ приходит от upstream (не статический файл, не
returnи не страница ошибки: статические файлы NGINX обрабатывает сам); - его статус —
200; - его тип перечислен в
dynamic_etag_types; - запрос не является
HEAD(нет тела для хеширования); - тело не пустое и помещается в
dynamic_etag_buffer_size; - upstream не отправил собственный
ETag. ТакойETagсохраняется, и NGINX отвечает на условные запросы на его основе.
Примечания
Страница должна быть одинаковой, чтобы получить одинаковый ETag. Страница,
которая содержит метку времени, случайный токен или nonce для каждого запроса, меняется
при каждом запросе, как и её хеш. Проверьте с помощью:
diff <(curl -s https://example.com/) <(curl -s https://example.com/) && echo stable
Ответ отправляется, когда он полностью готов. Клиент получает первый
байт только после того, как приложение сгенерировало последний. Для обычной страницы это
разница в несколько миллисекунд; для ответа, который намеренно передаётся потоком
(вывод прогресса, server-sent events), оставьте dynamic_etag выключенным.
Ответ, который upstream не смог завершить, не отправляется вовсе. Если
приложение падает посреди страницы, клиент получает закрытое соединение, а не половину страницы
с 200 и ETag.
Тестирование с помощью curl. curl -I отправляет запрос HEAD, который не получает
ETag. Используйте вместо этого curl -sI -X GET.
Условное использование
Директивы недопустимы внутри if. Используйте map:
map $request_uri $dyn_etag {
default on;
~^/checkout off;
~^/api/ off;
}
map $http_user_agent $etag_strength {
default strong;
~*legacybot weak;
}
server {
location / {
dynamic_etag $dyn_etag;
dynamic_etag_strength $etag_strength;
proxy_pass http://backend;
}
}