immerse: Модуль фильтра современного формата изображений NGINX
Требуется план Pro (или выше) подписки GetPageSpeed NGINX Extras.
Установка
Вы можете установить этот модуль в любой дистрибутив на базе 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-immerse
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-immerse
Включите модуль, добавив следующее в начало файла /etc/nginx/nginx.conf:
load_module modules/ngx_http_immerse_module.so;
Этот документ описывает nginx-module-immerse v1.0.2, выпущенный 5 апреля 2026 года.
Модуль фильтра NGINX для прозрачной доставки изображений в современном формате. Перехватывает
ответы с изображениями из любого источника (статические файлы, proxy_pass, FastCGI и т.д.)
и преобразует их в WebP или AVIF на основе заголовков Accept клиента. Без переписывания URL,
без отдельного сервиса, без изменений в приложении.
Как это работает
ngx_immerse вставляется в цепочку фильтров NGINX. Когда ответ с
Content-Type: image/jpeg, image/png или image/gif проходит через модуль, он
проверяет заголовок Accept клиента на поддержку современного формата. Если
совпадение найдено, он либо обслуживает кэшированное преобразование, либо запускает его через
пул потоков - не блокируя процессы рабочих.
Client Request NGINX
| |
|--- GET /photo.jpg ---------> |
| Accept: image/avif, |
| image/webp |
| |--- upstream / static file
| |<-- image/jpeg response
| |
| [ngx_immerse]
| |--- cache hit? serve cached avif
| |--- cache miss + lazy? serve jpeg,
| | queue background conversion
| |--- cache miss + sync? convert in
| | thread pool, serve avif
| |
|<-- 200 image/avif ---------- |
| Vary: Accept |
Особенности
- Прозрачная согласование формата - анализирует заголовок
Acceptв соответствии с RFC 7231 с поддержкой коэффициента качества (q=0отклоняет, наивысшийqвыигрывает) - Вывод в WebP и AVIF - настраиваемое качество, порядок приоритета и условная компиляция (собрать только один, если это необходимо)
- Кэш на основе файлов - с ключом MD5 и временем изменения исходного файла в ключе, так что кэш автоматически недействителен, когда оригинальное изображение изменяется
- Два режима преобразования -
lazy(обслужить оригинал сейчас, преобразовать в фоновом режиме) иsync(преобразовать встраиваемо, сразу обслужить современный формат) - Интеграция с пулом потоков - преобразования выполняются в пулах потоков NGINX, освобождая цикл событий
- Гладкое резервирование - сбой преобразования, поврежденные изображения, отсутствующий поток пул, полный диск: всегда обслуживает оригинал, никогда не возвращает 500
- Пороговые значения размера - пропускать изображения ниже
immerse_min_sizeили вышеimmerse_max_size, чтобы избежать потерь CPU на крошечных значках или огромных ресурсах - Безопасный для CDN - добавляет
Vary: Accept, чтобы кэши и CDN не обслуживали неправильный формат неправильному клиенту - Заголовок отладки -
X-Immerse: hit|miss|error|passпоказывает, что произошло (можно переключать) - Обнаружение магических байтов - определяет входной формат по сигнатуре файла, а не по расширению URL
Конфигурация
Минимальный пример
thread_pool immerse threads=4;
http {
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g;
server {
listen 80;
location /images/ {
immerse on;
immerse_thread_pool immerse;
alias /var/www/images/;
}
}
}
С проксируемым контентом
thread_pool immerse threads=4;
http {
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g;
server {
listen 80;
location /api/photos/ {
immerse on;
immerse_mode sync;
immerse_thread_pool immerse;
proxy_pass http://backend;
}
}
}
Полный пример со всеми директивами
thread_pool immerse threads=4;
http {
immerse_cache_path /var/cache/nginx/immerse levels=1:2
max_size=2g inactive=60d;
# По умолчанию для всех локаций
immerse_formats avif webp;
immerse_webp_quality 82;
immerse_avif_quality 63;
server {
listen 80;
# Статические изображения - режим lazy (по умолчанию)
location /images/ {
immerse on;
immerse_thread_pool immerse;
immerse_min_size 2k;
immerse_max_size 5m;
alias /var/www/images/;
}
# Проксируемые изображения - режим sync для немедленного преобразования
location /api/photos/ {
immerse on;
immerse_mode sync;
immerse_thread_pool immerse;
proxy_pass http://backend;
}
# Только WebP (без AVIF)
location /thumbnails/ {
immerse on;
immerse_formats webp;
immerse_webp_quality 75;
immerse_thread_pool immerse;
alias /var/www/thumbs/;
}
# Отключить заголовок отладки в производственной среде
location /cdn/ {
immerse on;
immerse_x_header off;
immerse_thread_pool immerse;
alias /var/www/cdn/;
}
}
}
Справочник директив
immerse
Синтаксис: immerse on | off;
По умолчанию: off
Контекст: location
Включает или отключает преобразование формата изображений для данной локации. Когда включено, модуль перехватывает ответы с изображениями и пытается преобразовать их на основе поддержки клиента.
Требуется, чтобы immerse_cache_path был установлен на уровне http. Если путь к кэшу
не настроен, модуль регистрирует ошибку и передает ответ без изменений.
immerse_cache_path
Синтаксис: immerse_cache_path path [levels=levels] [max_size=size] [inactive=time];
По умолчанию: нет (обязательно, когда immerse включен)
Контекст: http
Устанавливает каталог кэша и параметры. Эта директива обязательна - модуль не будет преобразовывать изображения без настроенного пути к кэшу.
Параметры:
- path - файловая директория для кэшированных преобразований. Создается автоматически, если не существует.
- levels - глубина иерархии подкаталогов, указанная в виде двоеточия-разделенных
цифр (1 или 2). По умолчанию:
1:2. Сlevels=1:2ключ кэшаa3b1c4d5e6...хранится по адресуpath/a/3b/a3b1c4d5e6....webp. - max_size - максимальный общий размер кэша. Принимает суффиксы размера (
k,m,g). По умолчанию: не установлен (без ограничений). - inactive - время, по истечении которого неиспользуемые кэшированные файлы могут быть
удалены. Принимает суффиксы времени (
s,m,h,d). По умолчанию:30d.
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g inactive=30d;
immerse_formats
Синтаксис: immerse_formats format ...;
По умолчанию: avif webp
Контекст: http, server, location
Устанавливает предпочтительные выходные форматы в порядке приоритета. Когда клиент принимает несколько форматов с равными коэффициентами качества, первый формат, указанный здесь, выигрывает.
Допустимые форматы: avif, webp. По крайней мере один из них должен быть поддержан на этапе компиляции.
# Предпочитать WebP перед AVIF
immerse_formats webp avif;
# Только WebP
immerse_formats webp;
immerse_mode
Синтаксис: immerse_mode lazy | sync;
По умолчанию: lazy
Контекст: location
Устанавливает стратегию преобразования для случаев, когда кэш не найден.
lazy - сразу обслуживает оригинальное изображение без задержки. Если исходный файл поддерживается, ставит в очередь фоновое преобразование в пуле потоков. Преобразованный вариант будет доступен для последующих запросов. Лучше всего подходит для обслуживания статических файлов, где важна задержка первого запроса.
sync - буферизует все тело ответа, преобразует его в пуле потоков и обслуживает преобразованное изображение в том же запросе. Рабочий не блокируется (пул потоков обрабатывает работу). Лучше всего подходит для проксируемого контента или когда вы хотите, чтобы каждый ответ был в современном формате.
# Статические файлы - lazy вполне подходит, кэш быстро заполняется
location /images/ {
immerse on;
immerse_mode lazy;
}
# Ответы API - sync гарантирует современный формат при первом запросе
location /api/photos/ {
immerse on;
immerse_mode sync;
proxy_pass http://backend;
}
immerse_webp_quality
Синтаксис: immerse_webp_quality quality;
По умолчанию: 80
Контекст: http, server, location
Качество кодирования WebP (1-100). Более высокие значения обеспечивают лучшее визуальное качество при больших размерах файлов. Значения около 75-85 обеспечивают хороший баланс для большинства контента.
immerse_avif_quality
Синтаксис: immerse_avif_quality quality;
По умолчанию: 60
Контекст: http, server, location
Качество кодирования AVIF (1-100). AVIF достигает хорошего визуального качества при более низких числовых значениях, чем WebP или JPEG. Значения около 50-70 типичны для веб-доставки. Кодировщик использует скорость 6 (сбалансированная скорость/качество).
immerse_min_size
Синтаксис: immerse_min_size size;
По умолчанию: 1k (1024 байта)
Контекст: http, server, location
Минимальный размер тела ответа для преобразования. Изображения меньше этого размера передаются без изменений. Это позволяет избежать потерь CPU на крошечных изображениях (favicon, 1x1 пиксели для отслеживания), где преобразование формата дает незначительную экономию.
immerse_max_size
Синтаксис: immerse_max_size size;
По умолчанию: 10m (10485760 байт)
Контекст: http, server, location
Максимальный размер тела ответа для преобразования. Изображения больше этого размера передаются без изменений. Это предотвращает исчерпание ресурсов из-за очень больших изображений, которые потребляют значительную память и CPU во время декодирования/кодирования.
immerse_thread_pool
Синтаксис: immerse_thread_pool name;
По умолчанию: default
Контекст: http, server, location
Имя пула потоков NGINX, который будет использоваться для задач преобразования. Должно совпадать с
директивой thread_pool в основном контексте конфигурации.
# Определите выделенный пул
thread_pool immerse threads=4;
http {
server {
location /images/ {
immerse on;
immerse_thread_pool immerse;
}
}
}
Рекомендации по размеру: начните с числа ядер CPU. Кодирование изображений зависит от CPU, поэтому большее количество потоков, чем ядер, не дает преимущества. Если тот же сервер обрабатывает другую работу пула потоков (aio), рассмотрите возможность создания выделенного пула для immerse.
immerse_x_header
Синтаксис: immerse_x_header on | off;
По умолчанию: on
Контекст: http, server, location
Управляет заголовком ответа X-Immerse. Когда включено, каждый обработанный
ответ включает заголовок, указывающий, что произошло:
| Значение | Значение |
|---|---|
hit |
Обслужено из кэша |
miss |
Промах по кэшу; преобразовано (sync) или оригинал обслужен (lazy) |
error |
Преобразование не удалось; оригинал обслужен в качестве резервного варианта |
Отключите это в производственной среде, если не хотите раскрывать внутреннее состояние модуля клиентам.
Заголовки ответа
Когда ngx_immerse обрабатывает ответ, он изменяет или добавляет следующие заголовки:
| Заголовок | Значение | Когда |
|---|---|---|
Content-Type |
image/webp или image/avif |
Преобразовано или обслужено из кэша |
Content-Length |
Размер преобразованного изображения | Преобразовано или обслужено из кэша |
Vary |
Accept |
Всегда (даже при пропуске) когда модуль активен |
X-Immerse |
hit, miss или error |
Когда immerse_x_header включен |
Заголовок Vary: Accept критически важен для правильного поведения CDN. Без него
CDN может кэшировать ответ WebP и обслуживать его клиенту, который поддерживает только
JPEG.
Парсинг заголовка Accept
Модуль анализирует заголовок запроса Accept в соответствии с RFC 7231:
- Извлекает записи
image/webpиimage/avifс их коэффициентами качества q=0означает, что клиент явно отклоняет этот форматq=1(или отсутствие параметраq) означает полную поддержку- Формат с наивысшим значением
qвыбирается - При равных
qвыигрывает первый формат вimmerse_formats - Если ни один формат не присутствует или оба имеют
q=0, обслуживается оригинал
Примеры:
| Заголовок Accept | Результат (с умолчательным immerse_formats avif webp) |
|---|---|
image/avif, image/webp |
AVIF (первый в конфигурации, равные q) |
image/webp |
WebP |
image/avif;q=0.8, image/webp;q=0.9 |
WebP (выше q) |
image/avif;q=0, image/webp |
WebP (AVIF отклонен) |
text/html, image/jpeg |
Оригинал (нет современного формата) |
Обнаружение входного формата
Исходные изображения определяются по магическим байтам в теле ответа, а не по расширению файла:
| Формат | Магические байты |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| GIF | GIF87a или GIF89a |
Изображения, уже находящиеся в формате WebP или AVIF, передаются без изменений. Анимированные GIF (несколько кадров) также передаются.
Кэш
Как это работает
Кэш хранит преобразованные изображения в иерархии каталогов, ключ которой - MD5 хэш.
Вход для хэша - URI + source_mtime + target_format + quality, так что:
- Разные форматы (WebP, AVIF) получают отдельные записи в кэше
- Изменение настроек качества создает новые записи в кэше
- Модификация оригинального изображения (изменение его времени) автоматически недействительна кэшированное преобразование
Структура каталогов
С levels=1:2 ключ кэша a3b1c4d5... создает:
/var/cache/nginx/immerse/a/3b/a3b1c4d5e6f7890123456789abcdef01.webp
Атомарные записи
Файлы кэша записываются атомарно: данные сначала идут в временный файл, затем
rename() перемещает его на место. Это предотвращает обслуживание частично записанных файлов
при одновременной нагрузке.
Прогрев кэша
В режиме lazy первый запрос к изображению обслуживает оригинал. Преобразование
работает в фоновом режиме, и последующие запросы получают кэшированный
современный формат. В режиме sync самый первый запрос запускает преобразование и
обслуживает результат.
Ручная очистка кэша
Чтобы очистить весь кэш:
rm -rf /var/cache/nginx/immerse/*
Перезагрузка NGINX не требуется. Модуль воссоздаст каталоги по мере необходимости.
Обработка ошибок
ngx_immerse следует строгой политике "никогда не ломать то, что уже работает":
| Условие | Поведение |
|---|---|
| Преобразование не удалось (ошибка кодека) | Обслужить оригинал, записать ошибку |
| Запись в кэш не удалась (диск полон, права доступа) | Обслужить преобразованное из памяти, записать предупреждение |
| Поврежденное или усеченное входное изображение | Обслужить оригинал, записать ошибку |
Изображение ниже min_size или выше max_size |
Передать без изменений |
Нет современного формата в Accept клиента |
Передать без изменений, добавить Vary: Accept |
| Пул потоков не найден | Вернуться к синхронному (блокирующему) преобразованию |
immerse_cache_path не настроен |
Передать без изменений, записать ошибку |
| Неизвестный формат изображения (не JPEG/PNG/GIF) | Передать без изменений |
Модуль никогда не вернет ошибку 500 из-за сбоя преобразования.
Архитектура
Исходные файлы
| Файл | Назначение |
|---|---|
config |
Интеграция с системой сборки NGINX, обнаружение библиотек |
src/ngx_http_immerse_common.h |
Общие типы, константы, объявления функций |
src/ngx_http_immerse_module.c |
Точка входа модуля, директивы, жизненный цикл конфигурации |
src/ngx_http_immerse_filter.c |
Цепочка фильтров заголовков и тела, конечный автомат, распределение потоков |
src/ngx_http_immerse_convert.c |
Движок декодирования/кодирования изображений (работает в пуле потоков) |
src/ngx_http_immerse_cache.c |
Генерация ключей кэша, поиск, атомарное хранение |
src/ngx_http_immerse_accept.c |
Парсер заголовка Accept RFC 7231 |
src/ngx_http_immerse_util.c |
Отправка ответа, буферизация тела, обнаружение формата, вспомогательные функции заголовков |
Безопасность потоков
Вся работа по преобразованию изображений выполняется в рабочих потоках пула NGINX. Код преобразования
(ngx_http_immerse_convert.c) использует только malloc/free, POSIX файловый ввод/вывод
и вызовы библиотек изображений. Он никогда не обращается к общему состоянию NGINX, пулам запросов
или циклу событий.
Результаты передаются обратно в основной цикл событий через стандартный механизм завершения задач потоков NGINX
(ngx_thread_task_t).
Конечный автомат
Фильтр тела использует конечный автомат на основе фаз:
START -> READ -> CONVERT -> SEND -> DONE (режим sync)
PASS -> DONE (режим lazy, первый запрос)
SERVE_CACHE -> DONE (попадание в кэш)
Тестирование
На основе Docker (рекомендуется)
# Запустить все тесты (режим HUP для ~10x более быстрой итерации)
make tests
# Запустить конкретный файл теста
make tests T=t/sync.t
# Запустить без режима HUP (чище состояние между тестами)
make tests HUP=0
# Интерактивная оболочка для отладки
make shell
# Пересобрать базовый образ (после изменений в Dockerfile)
make base-image
# Протестировать с другой версией NGINX
make tests NGINX_VERSION=release-1.26.2
CI
GitHub Actions запускает тесты против NGINX 1.26.2, 1.27.3 и 1.28.0 при каждом push и pull request.
Набор тестов
| Файл | Покрытие |
|---|---|
t/accept.t |
Парсинг заголовка Accept, значения q, выбор формата |
t/sync.t |
Преобразование в режиме sync для JPEG, PNG, GIF в WebP/AVIF |
t/lazy.t |
Режим lazy: оригинал обслуживается первым, кэш заполняется после |
t/cache.t |
Поведение попадания/промаха в кэш |
t/limits.t |
Фильтрация min_size и max_size |
t/fallback.t |
Резервирование при поврежденном изображении |
t/passthrough.t |
Модуль отключен, не изображен контент, нет заголовка Accept |
t/config.t |
Проверка директив, переключение x_header |
t/vary.t |
Наличие заголовка Vary: Accept |
Отладка
- Проверьте
test-error.logв корне репозитория для вывода отладки NGINX - Используйте
make shell, чтобы войти в контейнер и вручную запустить тесты - Уровень логирования установлен на
debugв тестовой среде Docker