Pular para conteúdo

immerse: Módulo de filtro de formato de imagem moderno do NGINX

Requer o plano Pro (ou superior) da assinatura GetPageSpeed NGINX Extras.

Instalação

Você pode instalar este módulo em qualquer distribuição baseada em RHEL, incluindo, mas não se limitando a:

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

Ative o módulo adicionando o seguinte no topo de /etc/nginx/nginx.conf:

load_module modules/ngx_http_immerse_module.so;

Este documento descreve o nginx-module-immerse v1.0.2 lançado em 05 de abril de 2026.


Módulo de filtro do NGINX para entrega transparente de formato de imagem moderno. Intercepta respostas de imagem de qualquer fonte (arquivos estáticos, proxy_pass, FastCGI, etc.) e as converte para WebP ou AVIF com base nos cabeçalhos Accept do cliente. Sem reescrita de URL, sem serviço separado, sem mudanças de aplicação.

Como Funciona

ngx_immerse se insere na cadeia de filtros do NGINX. Quando uma resposta com Content-Type: image/jpeg, image/png ou image/gif passa, o módulo verifica o cabeçalho Accept do cliente em busca de suporte a formatos modernos. Se uma correspondência for encontrada, ele serve uma conversão em cache ou aciona uma via um pool de threads - mantendo os processos de trabalho não bloqueantes.

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              |

Recursos

  • Negociação de formato transparente - analisa o cabeçalho Accept por RFC 7231 com suporte a fator de qualidade (q=0 rejeita, maior q vence)
  • Saída WebP e AVIF - qualidade configurável, ordem de prioridade e compilação condicional (construa com apenas um, se desejado)
  • Cache baseado em arquivo - com chave MD5 e mtime da fonte na chave, assim o cache se invalida automaticamente quando a imagem original muda
  • Dois modos de conversão - lazy (serve original agora, converte em segundo plano) e sync (converte inline, serve formato moderno imediatamente)
  • Integração com pool de threads - conversões são executadas em pools de threads do NGINX, mantendo o loop de eventos livre
  • Fallback gracioso - falha de conversão, imagens corrompidas, pool de threads ausente, disco cheio: sempre serve o original, nunca retorna 500
  • Limites de tamanho - ignora imagens abaixo de immerse_min_size ou acima de immerse_max_size para evitar desperdício de CPU em ícones pequenos ou ativos enormes
  • Seguro para CDN - adiciona Vary: Accept para que caches e CDNs não sirvam o formato errado para o cliente errado
  • Cabeçalho de depuração - X-Immerse: hit|miss|error|pass mostra o que aconteceu (ativável)
  • Detecção de byte mágico - identifica o formato de entrada pela assinatura do arquivo, não pela extensão da URL

Configuração

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

Com conteúdo proxy

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

Exemplo completo com todas as diretivas

thread_pool immerse threads=4;

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

    # Padrões para todas as localizações
    immerse_formats avif webp;
    immerse_webp_quality 82;
    immerse_avif_quality 63;

    server {
        listen 80;

        # Imagens estáticas - modo lazy (padrão)
        location /images/ {
            immerse on;
            immerse_thread_pool immerse;
            immerse_min_size 2k;
            immerse_max_size 5m;
            alias /var/www/images/;
        }

        # Imagens proxy - modo sync para conversão imediata
        location /api/photos/ {
            immerse on;
            immerse_mode sync;
            immerse_thread_pool immerse;
            proxy_pass http://backend;
        }

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

        # Desativar cabeçalho de depuração em produção
        location /cdn/ {
            immerse on;
            immerse_x_header off;
            immerse_thread_pool immerse;
            alias /var/www/cdn/;
        }
    }
}

Referência de Diretivas

immerse

Sintaxe: immerse on | off;
Padrão: off
Contexto: location

Habilita ou desabilita a conversão de formato de imagem para a localização. Quando habilitado, o módulo intercepta respostas de imagem e tenta a conversão com base no suporte do cliente.

Requer que immerse_cache_path seja definido no nível http. Se o caminho do cache não estiver configurado, o módulo registra um erro e passa a resposta sem alterações.

immerse_cache_path

Sintaxe: immerse_cache_path path [levels=levels] [max_size=size] [inactive=time];
Padrão: nenhum (obrigatório quando immerse está habilitado)
Contexto: http

Define o diretório e os parâmetros do cache. Esta diretiva é obrigatória - o módulo não converterá imagens sem um caminho de cache configurado.

Parâmetros:

  • path - diretório do sistema de arquivos para conversões em cache. Criado automaticamente se não existir.
  • levels - profundidade da hierarquia de subdiretórios, especificada como dígitos separados por dois pontos (1 ou 2). Padrão: 1:2. Com levels=1:2, uma chave de cache a3b1c4d5e6... é armazenada em path/a/3b/a3b1c4d5e6....webp.
  • max_size - tamanho total máximo do cache. Aceita sufixos de tamanho (k, m, g). Padrão: não definido (sem limite).
  • inactive - tempo após o qual arquivos em cache não utilizados são elegíveis para remoção. Aceita sufixos de tempo (s, m, h, d). Padrão: 30d.
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g inactive=30d;

immerse_formats

Sintaxe: immerse_formats format ...;
Padrão: avif webp
Contexto: http, server, location

Define os formatos de saída preferidos em ordem de prioridade. Quando múltiplos formatos são aceitos pelo cliente com fatores de qualidade iguais, o primeiro formato listado aqui vence.

Formatos válidos: avif, webp. Pelo menos um deve ser suportado em tempo de compilação.

# Preferir WebP sobre AVIF
immerse_formats webp avif;

# Apenas WebP
immerse_formats webp;

immerse_mode

Sintaxe: immerse_mode lazy | sync;
Padrão: lazy
Contexto: location

Define a estratégia de conversão para falhas de cache.

lazy - serve a imagem original imediatamente sem sobrecarga de latência. Se a fonte da imagem for baseada em arquivo, coloca uma conversão em segundo plano na fila no pool de threads. A variante convertida estará disponível para solicitações subsequentes. Melhor para servir arquivos estáticos onde a latência da primeira solicitação importa.

sync - armazena em buffer todo o corpo da resposta, converte em um pool de threads, e serve a imagem convertida na mesma solicitação. O trabalhador não é bloqueado (o pool de threads lida com o trabalho). Melhor para conteúdo proxy ou quando você deseja que cada resposta esteja em um formato moderno.

# Arquivos estáticos - lazy é suficiente, cache aquece rapidamente
location /images/ {
    immerse on;
    immerse_mode lazy;
}

# Respostas da API - sync garante formato moderno na primeira solicitação
location /api/photos/ {
    immerse on;
    immerse_mode sync;
    proxy_pass http://backend;
}

immerse_webp_quality

Sintaxe: immerse_webp_quality quality;
Padrão: 80
Contexto: http, server, location

Qualidade de codificação WebP (1-100). Valores mais altos produzem melhor qualidade visual em tamanhos de arquivo maiores. Valores em torno de 75-85 proporcionam um bom equilíbrio para a maioria do conteúdo.

immerse_avif_quality

Sintaxe: immerse_avif_quality quality;
Padrão: 60
Contexto: http, server, location

Qualidade de codificação AVIF (1-100). AVIF alcança boa qualidade visual em valores numéricos mais baixos do que WebP ou JPEG. Valores em torno de 50-70 são típicos para entrega na web. O codificador usa velocidade 6 (equilíbrio entre velocidade/qualidade).

immerse_min_size

Sintaxe: immerse_min_size size;
Padrão: 1k (1024 bytes)
Contexto: http, server, location

Tamanho mínimo do corpo da resposta para conversão. Imagens menores que isso são passadas sem alterações. Isso evita desperdício de CPU em imagens pequenas (favicons, pixels de rastreamento 1x1) onde a conversão de formato proporciona economias negligenciáveis.

immerse_max_size

Sintaxe: immerse_max_size size;
Padrão: 10m (10485760 bytes)
Contexto: http, server, location

Tamanho máximo do corpo da resposta para conversão. Imagens maiores que isso são passadas sem alterações. Isso previne a exaustão de recursos de imagens muito grandes que consumiriam memória e CPU significativas durante a decodificação/encodificação.

immerse_thread_pool

Sintaxe: immerse_thread_pool name;
Padrão: default
Contexto: http, server, location

Nome do pool de threads do NGINX a ser usado para tarefas de conversão. Deve corresponder a uma diretiva thread_pool no contexto de configuração principal.

# Definir um pool dedicado
thread_pool immerse threads=4;

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

Orientação de dimensionamento: comece com o número de núcleos de CPU. A codificação de imagens é dependente de CPU, portanto, mais threads do que núcleos não traz benefícios. Se o mesmo servidor lidar com outros trabalhos de pool de threads (aio), considere um pool dedicado para immerse.

immerse_x_header

Sintaxe: immerse_x_header on | off;
Padrão: on
Contexto: http, server, location

Controla o cabeçalho de resposta X-Immerse. Quando habilitado, cada resposta processada inclui um cabeçalho indicando o que aconteceu:

Valor Significado
hit Servido do cache
miss Falha de cache; convertido (sync) ou original servido (lazy)
error Falha na conversão; original servido como fallback

Desative isso em produção se você não quiser expor o estado interno do módulo aos clientes.

Cabeçalhos de Resposta

Quando ngx_immerse processa uma resposta, ele modifica ou adiciona os seguintes cabeçalhos:

Cabeçalho Valor Quando
Content-Type image/webp ou image/avif Convertido ou servido do cache
Content-Length Tamanho da imagem convertida Convertido ou servido do cache
Vary Accept Sempre (mesmo em passagem) quando o módulo está ativo
X-Immerse hit, miss ou error Quando immerse_x_header está ativado

O cabeçalho Vary: Accept é crítico para o comportamento correto da CDN. Sem ele, uma CDN pode armazenar em cache uma resposta WebP e servi-la a um cliente que só suporta JPEG.

Análise do Cabeçalho Accept

O módulo analisa o cabeçalho de solicitação Accept por RFC 7231:

  • Extrai entradas image/webp e image/avif com seus fatores de qualidade
  • q=0 significa que o cliente rejeita explicitamente esse formato
  • q=1 (ou nenhum parâmetro q) significa suporte total
  • O formato com o maior valor q é selecionado
  • Em q iguais, o primeiro formato em immerse_formats vence
  • Se nenhum formato estiver presente ou ambos tiverem q=0, o original é servido

Exemplos:

Cabeçalho Accept Resultado (com o padrão immerse_formats avif webp)
image/avif, image/webp AVIF (primeiro na configuração, q igual)
image/webp WebP
image/avif;q=0.8, image/webp;q=0.9 WebP (q mais alto)
image/avif;q=0, image/webp WebP (AVIF rejeitado)
text/html, image/jpeg Original (sem formato moderno)

Detecção de Formato de Entrada

Imagens de origem são identificadas por bytes mágicos no corpo da resposta, não pela extensão do arquivo:

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

Imagens já no formato WebP ou AVIF são passadas sem alterações. GIFs animados (múltiplos quadros) também são passados.

Cache

Como funciona

O cache armazena imagens convertidas em uma hierarquia de diretórios com chave MD5. A entrada do hash é URI + source_mtime + target_format + quality, então:

  • Diferentes formatos (WebP, AVIF) obtêm entradas de cache separadas
  • Alterar configurações de qualidade produz novas entradas de cache
  • Modificar a imagem original (alterando seu mtime) invalida automaticamente a conversão em cache

Layout do diretório

Com levels=1:2, uma chave de cache a3b1c4d5... produz:

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

Escritas atômicas

Os arquivos de cache são escritos de forma atômica: os dados vão primeiro para um arquivo temporário, depois rename() move-o para o lugar. Isso evita servir arquivos parcialmente escritos sob carga concorrente.

Aquecimento do cache

No modo lazy, a primeira solicitação para uma imagem serve o original. A conversão é executada em segundo plano, e solicitações subsequentes recebem o formato moderno em cache. No modo sync, a primeira solicitação aciona a conversão e serve o resultado.

Limpeza manual do cache

Para limpar todo o cache:

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

Nenhum recarregamento do NGINX é necessário. O módulo recriará diretórios conforme necessário.

Tratamento de Erros

ngx_immerse segue uma política estrita de "nunca quebrar o que já funciona":

Condição Comportamento
Falha na conversão (erro de codec) Serve original, registra erro
Falha na gravação do cache (disco cheio, permissões) Serve convertido da memória, registra aviso
Imagem de entrada corrompida ou truncada Serve original, registra erro
Imagem abaixo de min_size ou acima de max_size Passa sem alterações
Sem formato moderno no Accept do cliente Passa sem alterações, adiciona Vary: Accept
Pool de threads não encontrado Reverte para conversão síncrona (bloqueante)
immerse_cache_path não configurado Passa sem alterações, registra erro
Formato de imagem desconhecido (não JPEG/PNG/GIF) Passa sem alterações

O módulo nunca retornará um erro 500 devido a uma falha de conversão.

Arquitetura

Arquivos de origem

Arquivo Propósito
config Integração com o sistema de build do NGINX, detecção de biblioteca
src/ngx_http_immerse_common.h Tipos compartilhados, constantes, declarações de função
src/ngx_http_immerse_module.c Ponto de entrada do módulo, diretivas, ciclo de vida da configuração
src/ngx_http_immerse_filter.c Cadeia de filtros de cabeçalho e corpo, máquina de estados, despacho de pool de threads
src/ngx_http_immerse_convert.c Motor de decodificação/encodificação de imagem (executa no pool de threads)
src/ngx_http_immerse_cache.c Geração de chave de cache, pesquisa, armazenamento atômico
src/ngx_http_immerse_accept.c Analisador de cabeçalho Accept RFC 7231
src/ngx_http_immerse_util.c Envio de resposta, buffer de corpo, detecção de formato, auxiliares de cabeçalho

Segurança de threads

Todo o trabalho de conversão de imagem é executado em trabalhadores de pool de threads do NGINX. O código de conversão (ngx_http_immerse_convert.c) usa apenas malloc/free, E/S de arquivos POSIX, e chamadas de biblioteca de imagem. Ele nunca acessa o estado compartilhado do NGINX, pools de solicitações, ou o loop de eventos.

Os resultados são passados de volta ao loop de eventos principal via o mecanismo padrão de conclusão de tarefas de thread do NGINX (ngx_thread_task_t).

Máquina de estados

O filtro de corpo usa uma máquina de estados baseada em fases:

START -> READ -> CONVERT -> SEND -> DONE     (modo sync)
PASS -> DONE                                  (modo lazy, primeira solicitação)
SERVE_CACHE -> DONE                           (cache hit)

Testes

Baseado em Docker (recomendado)

# Executar todos os testes (modo HUP para ~10x mais rápido)
make tests

# Executar um arquivo de teste específico
make tests T=t/sync.t

# Executar sem modo HUP (estado mais limpo entre os testes)
make tests HUP=0

# Shell interativa para depuração
make shell

# Reconstruir imagem base (após alterações no Dockerfile)
make base-image

# Testar contra uma versão diferente do NGINX
make tests NGINX_VERSION=release-1.26.2

CI

GitHub Actions executa testes contra NGINX 1.26.2, 1.27.3 e 1.28.0 em cada push e pull request.

Conjunto de testes

Arquivo Cobertura
t/accept.t Análise do cabeçalho Accept, valores q, seleção de formato
t/sync.t Conversão em modo sync para JPEG, PNG, GIF para WebP/AVIF
t/lazy.t Modo lazy: original servido primeiro, cache populado depois
t/cache.t Comportamento de cache hit/miss
t/limits.t Filtragem de min_size e max_size
t/fallback.t Fallback de imagem corrompida
t/passthrough.t Módulo desativado, conteúdo não-imagem, sem cabeçalho Accept
t/config.t Validação de diretivas, alternância de x_header
t/vary.t Presença do cabeçalho Vary: Accept

Depuração

  • Verifique test-error.log na raiz do repositório para saída de depuração do NGINX
  • Use make shell para entrar no contêiner e executar testes manualmente
  • O nível de log é definido como debug no ambiente de teste Docker