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
Acceptpor RFC 7231 com suporte a fator de qualidade (q=0rejeita, maiorqvence) - 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) esync(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_sizeou acima deimmerse_max_sizepara evitar desperdício de CPU em ícones pequenos ou ativos enormes - Seguro para CDN - adiciona
Vary: Acceptpara que caches e CDNs não sirvam o formato errado para o cliente errado - Cabeçalho de depuração -
X-Immerse: hit|miss|error|passmostra 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. Comlevels=1:2, uma chave de cachea3b1c4d5e6...é armazenada empath/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/webpeimage/avifcom seus fatores de qualidade q=0significa que o cliente rejeita explicitamente esse formatoq=1(ou nenhum parâmetroq) significa suporte total- O formato com o maior valor
qé selecionado - Em
qiguais, o primeiro formato emimmerse_formatsvence - 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.logna raiz do repositório para saída de depuração do NGINX - Use
make shellpara entrar no contêiner e executar testes manualmente - O nível de log é definido como
debugno ambiente de teste Docker