Перейти к содержанию

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;
    }
}