Saltar a contenido

immerse: Módulo de filtro de formato de imagen moderno de NGINX

Requiere el plan Pro (o superior) de la suscripción de NGINX Extras de GetPageSpeed.

Instalación

Puedes instalar este módulo en cualquier distribución basada en RHEL, incluyendo, pero no limitado a:

  • RedHat Enterprise Linux 7, 8, 9 y 10
  • CentOS 7, 8, 9
  • AlmaLinux 8, 9
  • Rocky Linux 8, 9
  • Amazon Linux 2 y 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

Habilita el módulo añadiendo lo siguiente en la parte superior de /etc/nginx/nginx.conf:

load_module modules/ngx_http_immerse_module.so;

Este documento describe nginx-module-immerse v1.0.2 lanzado el 05 de abril de 2026.


Módulo de filtro de NGINX para la entrega transparente de formatos de imagen modernos. Intercepta respuestas de imágenes de cualquier fuente (archivos estáticos, proxy_pass, FastCGI, etc.) y las convierte a WebP o AVIF según los encabezados Accept del cliente. Sin reescritura de URL, sin servicio separado, sin cambios en la aplicación.

Cómo Funciona

ngx_immerse se inserta en la cadena de filtros de NGINX. Cuando una respuesta con Content-Type: image/jpeg, image/png o image/gif pasa a través, el módulo verifica el encabezado Accept del cliente en busca de soporte para formatos modernos. Si se encuentra una coincidencia, sirve una conversión en caché o activa una a través de un grupo de hilos, manteniendo los procesos de trabajo no bloqueantes.

Solicitud del Cliente                    NGINX
     |                                   |
     |--- GET /photo.jpg -------------> |
     |    Accept: image/avif,            |
     |    image/webp                     |
     |                                   |--- upstream / archivo estático
     |                                   |<-- respuesta image/jpeg
     |                                   |
     |                            [ngx_immerse]
     |                                   |--- ¿acierto en caché? servir avif en caché
     |                                   |--- ¿fallo en caché + perezoso? servir jpeg,
     |                                   |    encolar conversión en segundo plano
     |                                   |--- ¿fallo en caché + sincrónico? convertir en
     |                                   |    grupo de hilos, servir avif
     |                                   |
     |<-- 200 image/avif --------------- |
     |    Vary: Accept                   |

Características

  • Negociación de formato transparente - analiza el encabezado Accept según la RFC 7231 con soporte para factores de calidad (q=0 rechaza, el mayor q gana)
  • Salida en WebP y AVIF - calidad configurable, orden de prioridad y compilación condicional (compilar solo uno si se desea)
  • Caché basado en archivos - con clave MD5 y mtime de origen en la clave, por lo que la caché se invalida automáticamente cuando la imagen original cambia
  • Dos modos de conversión - lazy (servir original ahora, convertir en segundo plano) y sync (convertir en línea, servir formato moderno inmediatamente)
  • Integración con grupo de hilos - las conversiones se ejecutan en grupos de hilos de NGINX, manteniendo el bucle de eventos libre
  • Retroceso elegante - fallo en la conversión, imágenes corruptas, grupo de hilos faltante, disco lleno: siempre sirve el original, nunca devuelve 500
  • Umbrales de tamaño - omitir imágenes por debajo de immerse_min_size o por encima de immerse_max_size para evitar desperdiciar CPU en íconos pequeños o activos enormes
  • Seguro para CDN - añade Vary: Accept para que las cachés y CDNs no sirvan el formato incorrecto al cliente incorrecto
  • Encabezado de depuración - X-Immerse: hit|miss|error|pass muestra lo que sucedió (conmutable)
  • Detección de bytes mágicos - identifica el formato de entrada por la firma del archivo, no por la extensión de la URL

Configuración

Ejemplo mínimo

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

Con contenido proxied

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

Ejemplo completo con todas las directivas

thread_pool immerse threads=4;

http {
    immerse_cache_path /var/cache/nginx/immerse levels=1:2
                       max_size=2g inactive=60d;

    # Valores predeterminados para todas las ubicaciones
    immerse_formats avif webp;
    immerse_webp_quality 82;
    immerse_avif_quality 63;

    server {
        listen 80;

        # Imágenes estáticas - modo perezoso (predeterminado)
        location /images/ {
            immerse on;
            immerse_thread_pool immerse;
            immerse_min_size 2k;
            immerse_max_size 5m;
            alias /var/www/images/;
        }

        # Imágenes proxied - modo sincrónico para conversión inmediata
        location /api/photos/ {
            immerse on;
            immerse_mode sync;
            immerse_thread_pool immerse;
            proxy_pass http://backend;
        }

        # Solo WebP (sin AVIF)
        location /thumbnails/ {
            immerse on;
            immerse_formats webp;
            immerse_webp_quality 75;
            immerse_thread_pool immerse;
            alias /var/www/thumbs/;
        }

        # Desactivar encabezado de depuración en producción
        location /cdn/ {
            immerse on;
            immerse_x_header off;
            immerse_thread_pool immerse;
            alias /var/www/cdn/;
        }
    }
}

Referencia de Directivas

immerse

Sintaxis: immerse on | off;
Predeterminado: off
Contexto: location

Habilita o deshabilita la conversión de formato de imagen para la ubicación. Cuando está habilitado, el módulo intercepta respuestas de imágenes e intenta la conversión según el soporte del cliente.

Requiere que immerse_cache_path esté configurado a nivel de http. Si la ruta de la caché no está configurada, el módulo registra un error y pasa la respuesta sin cambios.

immerse_cache_path

Sintaxis: immerse_cache_path path [levels=levels] [max_size=size] [inactive=time];
Predeterminado: ninguno (requerido cuando immerse está habilitado)
Contexto: http

Establece el directorio de caché y los parámetros. Esta directiva es obligatoria: el módulo no convertirá imágenes sin una ruta de caché configurada.

Parámetros:

  • path - directorio del sistema de archivos para conversiones en caché. Se crea automáticamente si no existe.
  • levels - profundidad de la jerarquía de subdirectorios, especificada como dígitos separados por dos puntos (1 o 2). Predeterminado: 1:2. Con levels=1:2, una clave de caché a3b1c4d5e6... se almacena en path/a/3b/a3b1c4d5e6....webp.
  • max_size - tamaño total máximo de la caché. Acepta sufijos de tamaño (k, m, g). Predeterminado: no establecido (sin límite).
  • inactive - tiempo después del cual los archivos en caché no utilizados son elegibles para eliminación. Acepta sufijos de tiempo (s, m, h, d). Predeterminado: 30d.
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g inactive=30d;

immerse_formats

Sintaxis: immerse_formats format ...;
Predeterminado: avif webp
Contexto: http, server, location

Establece los formatos de salida preferidos en orden de prioridad. Cuando el cliente acepta múltiples formatos con factores de calidad iguales, el primer formato listado aquí gana.

Formatos válidos: avif, webp. Al menos uno debe ser compatible en tiempo de compilación.

# Preferir WebP sobre AVIF
immerse_formats webp avif;

# Solo WebP
immerse_formats webp;

immerse_mode

Sintaxis: immerse_mode lazy | sync;
Predeterminado: lazy
Contexto: location

Establece la estrategia de conversión para fallos en la caché.

lazy - sirve la imagen original inmediatamente sin sobrecarga de latencia. Si la fuente de la imagen está respaldada por un archivo, encola una conversión en segundo plano en el grupo de hilos. La variante convertida está disponible para solicitudes posteriores. Mejor para servir archivos estáticos donde la latencia de la primera solicitud importa.

sync - almacena en búfer todo el cuerpo de la respuesta, lo convierte en un grupo de hilos y sirve la imagen convertida en la misma solicitud. El trabajador no se bloquea (el grupo de hilos maneja el trabajo). Mejor para contenido proxied o cuando deseas que cada respuesta esté en un formato moderno.

# Archivos estáticos - lazy está bien, la caché se calienta rápidamente
location /images/ {
    immerse on;
    immerse_mode lazy;
}

# Respuestas de API - sync asegura formato moderno en la primera solicitud
location /api/photos/ {
    immerse on;
    immerse_mode sync;
    proxy_pass http://backend;
}

immerse_webp_quality

Sintaxis: immerse_webp_quality quality;
Predeterminado: 80
Contexto: http, server, location

Calidad de codificación WebP (1-100). Valores más altos producen mejor calidad visual a tamaños de archivo más grandes. Valores alrededor de 75-85 proporcionan un buen equilibrio para la mayoría del contenido.

immerse_avif_quality

Sintaxis: immerse_avif_quality quality;
Predeterminado: 60
Contexto: http, server, location

Calidad de codificación AVIF (1-100). AVIF logra buena calidad visual a valores numéricos más bajos que WebP o JPEG. Valores alrededor de 50-70 son típicos para la entrega web. El codificador utiliza velocidad 6 (equilibrio entre velocidad/calidad).

immerse_min_size

Sintaxis: immerse_min_size size;
Predeterminado: 1k (1024 bytes)
Contexto: http, server, location

Tamaño mínimo del cuerpo de respuesta para la conversión. Las imágenes más pequeñas que esto se pasan sin cambios. Esto evita desperdiciar CPU en imágenes pequeñas (favicon, píxeles de seguimiento 1x1) donde la conversión de formato proporciona ahorros insignificantes.

immerse_max_size

Sintaxis: immerse_max_size size;
Predeterminado: 10m (10485760 bytes)
Contexto: http, server, location

Tamaño máximo del cuerpo de respuesta para la conversión. Las imágenes más grandes que esto se pasan sin cambios. Esto previene el agotamiento de recursos por imágenes muy grandes que consumirían una cantidad significativa de memoria y CPU durante la decodificación/encoding.

immerse_thread_pool

Sintaxis: immerse_thread_pool name;
Predeterminado: default
Contexto: http, server, location

Nombre del grupo de hilos de NGINX a utilizar para tareas de conversión. Debe coincidir con una directiva thread_pool en el contexto de configuración principal.

# Definir un grupo dedicado
thread_pool immerse threads=4;

http {
    server {
        location /images/ {
            immerse on;
            immerse_thread_pool immerse;
        }
    }
}

Orientación sobre el tamaño: comienza con el número de núcleos de CPU. La codificación de imágenes está limitada por la CPU, por lo que más hilos que núcleos no proporciona ningún beneficio. Si el mismo servidor maneja otro trabajo de grupo de hilos (aio), considera un grupo dedicado para immerse.

immerse_x_header

Sintaxis: immerse_x_header on | off;
Predeterminado: on
Contexto: http, server, location

Controla el encabezado de respuesta X-Immerse. Cuando está habilitado, cada respuesta procesada incluye un encabezado que indica lo que sucedió:

Valor Significado
hit Servido desde caché
miss Fallo en caché; convertido (sincrónico) o original servido (perezoso)
error Fallo en la conversión; original servido como retroceso

Desactiva esto en producción si no deseas exponer el estado interno del módulo a los clientes.

Encabezados de Respuesta

Cuando ngx_immerse procesa una respuesta, modifica o añade los siguientes encabezados:

Encabezado Valor Cuándo
Content-Type image/webp o image/avif Convertido o servido desde caché
Content-Length Tamaño de la imagen convertida Convertido o servido desde caché
Vary Accept Siempre (incluso en el paso) cuando el módulo está activo
X-Immerse hit, miss o error Cuando immerse_x_header está activado

El encabezado Vary: Accept es crítico para el comportamiento correcto de la CDN. Sin él, una CDN podría almacenar en caché una respuesta WebP y servirla a un cliente que solo admite JPEG.

Análisis del Encabezado Accept

El módulo analiza el encabezado de solicitud Accept según la RFC 7231:

  • Extrae entradas image/webp y image/avif con sus factores de calidad
  • q=0 significa que el cliente rechaza explícitamente ese formato
  • q=1 (o sin parámetro q) significa soporte total
  • Se selecciona el formato con el mayor valor q
  • En caso de q iguales, el primer formato en immerse_formats gana
  • Si ninguno de los formatos está presente o ambos tienen q=0, se sirve el original

Ejemplos:

Encabezado Accept Resultado (con immerse_formats avif webp por defecto)
image/avif, image/webp AVIF (primero en la configuración, q igual)
image/webp WebP
image/avif;q=0.8, image/webp;q=0.9 WebP (q más alto)
image/avif;q=0, image/webp WebP (AVIF rechazado)
text/html, image/jpeg Original (sin formato moderno)

Detección del Formato de Entrada

Las imágenes de origen se identifican por bytes mágicos en el cuerpo de la respuesta, no por la extensión del archivo:

Formato Bytes mágicos
JPEG FF D8 FF
PNG 89 50 4E 47 0D 0A 1A 0A
GIF GIF87a o GIF89a

Las imágenes que ya están en formato WebP o AVIF se pasan sin cambios. Los GIF animados (múltiples fotogramas) también se pasan sin cambios.

Caché

Cómo funciona

La caché almacena imágenes convertidas en una jerarquía de directorios clave por hash MD5. La entrada del hash es URI + source_mtime + target_format + quality, por lo que:

  • Diferentes formatos (WebP, AVIF) obtienen entradas de caché separadas
  • Cambiar la configuración de calidad produce nuevas entradas de caché
  • Modificar la imagen original (cambiando su mtime) invalida automáticamente la conversión en caché

Diseño del directorio

Con levels=1:2, una clave de caché a3b1c4d5... produce:

/var/cache/nginx/immerse/a/3b/a3b1c4d5e6f7890123456789abcdef01.webp

Escrituras atómicas

Los archivos de caché se escriben de forma atómica: los datos van primero a un archivo temporal, luego rename() lo mueve a su lugar. Esto previene servir archivos parcialmente escritos bajo carga concurrente.

Calentamiento de caché

En modo lazy, la primera solicitud para una imagen sirve el original. La conversión se ejecuta en segundo plano, y las solicitudes posteriores obtienen el formato moderno en caché. En modo sync, la primera solicitud activa la conversión y sirve el resultado.

Limpieza manual de caché

Para limpiar toda la caché:

rm -rf /var/cache/nginx/immerse/*

No se requiere recarga de NGINX. El módulo recreará los directorios según sea necesario.

Manejo de Errores

ngx_immerse sigue una estricta política de "nunca romper lo que ya funciona":

Condición Comportamiento
La conversión falla (error de códec) Servir original, registrar error
La escritura en caché falla (disco lleno, permisos) Servir convertido desde memoria, registrar advertencia
Imagen de entrada corrupta o truncada Servir original, registrar error
Imagen por debajo de min_size o por encima de max_size Pasar sin cambios
Sin formato moderno en Accept del cliente Pasar sin cambios, añadir Vary: Accept
Grupo de hilos no encontrado Retroceder a conversión sincrónica (bloqueante)
immerse_cache_path no configurado Pasar sin cambios, registrar error
Formato de imagen desconocido (no JPEG/PNG/GIF) Pasar sin cambios

El módulo nunca devolverá un error 500 debido a un fallo en la conversión.

Arquitectura

Archivos fuente

Archivo Propósito
config Integración del sistema de construcción de NGINX, detección de bibliotecas
src/ngx_http_immerse_common.h Tipos compartidos, constantes, declaraciones de funciones
src/ngx_http_immerse_module.c Punto de entrada del módulo, directivas, ciclo de vida de la configuración
src/ngx_http_immerse_filter.c Cadena de filtros de encabezado y cuerpo, máquina de estados, despacho de grupo de hilos
src/ngx_http_immerse_convert.c Motor de decodificación/codificación de imágenes (se ejecuta en el grupo de hilos)
src/ngx_http_immerse_cache.c Generación de claves de caché, búsqueda, almacenamiento atómico
src/ngx_http_immerse_accept.c Analizador de encabezado Accept según RFC 7231
src/ngx_http_immerse_util.c Envío de respuestas, almacenamiento en búfer de cuerpos, detección de formatos, ayudantes de encabezados

Seguridad de hilos

Todo el trabajo de conversión de imágenes se ejecuta en trabajadores de grupos de hilos de NGINX. El código de conversión (ngx_http_immerse_convert.c) utiliza solo malloc/free, E/S de archivos POSIX y llamadas a bibliotecas de imágenes. Nunca accede al estado compartido de NGINX, a los grupos de solicitudes o al bucle de eventos.

Los resultados se devuelven al bucle de eventos principal a través del mecanismo estándar de finalización de tareas de hilos de NGINX (ngx_thread_task_t).

Máquina de estados

El filtro de cuerpo utiliza una máquina de estados basada en fases:

START -> READ -> CONVERT -> SEND -> DONE     (modo sincrónico)
PASS -> DONE                                  (modo perezoso, primera solicitud)
SERVE_CACHE -> DONE                           (acierto en caché)

Pruebas

Basado en Docker (recomendado)

# Ejecutar todas las pruebas (modo HUP para iteración ~10x más rápida)
make tests

# Ejecutar un archivo de prueba específico
make tests T=t/sync.t

# Ejecutar sin modo HUP (estado más limpio entre pruebas)
make tests HUP=0

# Shell interactivo para depuración
make shell

# Reconstruir imagen base (después de cambios en Dockerfile)
make base-image

# Probar contra una versión diferente de NGINX
make tests NGINX_VERSION=release-1.26.2

CI

GitHub Actions ejecuta pruebas contra NGINX 1.26.2, 1.27.3 y 1.28.0 en cada push y pull request.

Suite de pruebas

Archivo Cobertura
t/accept.t Análisis del encabezado Accept, valores q, selección de formato
t/sync.t Conversión en modo sincrónico para JPEG, PNG, GIF a WebP/AVIF
t/lazy.t Modo perezoso: original servido primero, caché poblada después
t/cache.t Comportamiento de acierto/fallo en caché
t/limits.t Filtrado de min_size y max_size
t/fallback.t Retroceso de imagen corrupta
t/passthrough.t Módulo desactivado, contenido no imagen, sin encabezado Accept
t/config.t Validación de directivas, conmutación de x_header
t/vary.t Presencia del encabezado Vary: Accept

Depuración

  • Verifica test-error.log en la raíz del repositorio para la salida de depuración de NGINX
  • Usa make shell para entrar en el contenedor y ejecutar pruebas manualmente
  • El nivel de registro se establece en debug en el entorno de prueba de Docker