Zum Inhalt

immerse: NGINX Modul für moderne Bildformatfilter

Erfordert den Pro-Plan (oder höher) des GetPageSpeed NGINX Extras Abonnements.

Installation

Sie können dieses Modul in jeder RHEL-basierten Distribution installieren, einschließlich, aber nicht beschränkt auf:

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

Aktivieren Sie das Modul, indem Sie Folgendes an den Anfang von /etc/nginx/nginx.conf hinzufügen:

load_module modules/ngx_http_immerse_module.so;

Dieses Dokument beschreibt nginx-module-immerse v1.0.2, veröffentlicht am 05. April 2026.


NGINX Filtermodul für die transparente Bereitstellung moderner Bildformate. Interceptiert Bildantworten aus jeder Quelle (statische Dateien, proxy_pass, FastCGI usw.) und konvertiert sie basierend auf den Accept-Headern des Clients in WebP oder AVIF. Keine URL Umleitungen, kein separater Dienst, keine Änderungen an der Anwendung.

Funktionsweise

ngx_immerse wird in die NGINX Filterkette eingefügt. Wenn eine Antwort mit Content-Type: image/jpeg, image/png oder image/gif durchläuft, überprüft das Modul den Accept-Header des Clients auf Unterstützung moderner Formate. Wenn eine Übereinstimmung gefunden wird, wird entweder eine zwischengespeicherte Konvertierung bereitgestellt oder eine über einen Thread-Pool ausgelöst - die Arbeitsprozesse bleiben nicht blockiert.

Client-Anfrage                    NGINX
     |                              |
     |--- GET /photo.jpg ---------> |
     |    Accept: image/avif,       |
     |    image/webp                |
     |                              |--- Upstream / statische Datei
     |                              |<-- image/jpeg Antwort
     |                              |
     |                         [ngx_immerse]
     |                              |--- Cache-Treffer? cached avif bereitstellen
     |                              |--- Cache-Fehlgeschlagen + lazy? jpeg bereitstellen,
     |                              |    Hintergrundkonvertierung in die Warteschlange stellen
     |                              |--- Cache-Fehlgeschlagen + sync? in
     |                              |    Thread-Pool konvertieren, avif bereitstellen
     |                              |
     |<-- 200 image/avif ---------- |
     |    Vary: Accept              |

Funktionen

  • Transparente Formatverhandlung - analysiert den Accept-Header gemäß RFC 7231 mit Unterstützung für Qualitätsfaktoren (q=0 lehnt ab, höchstes q gewinnt)
  • WebP und AVIF Ausgabe - konfigurierbare Qualität, Prioritätsreihenfolge und bedingte Kompilierung (nur mit einem bauen, wenn gewünscht)
  • Dateibasierter Cache - MD5-verschlüsselt mit der Quell-mtime im Schlüssel, sodass der Cache automatisch ungültig wird, wenn sich das Originalbild ändert
  • Zwei Konvertierungsmodi - lazy (jetzt Original bereitstellen, im Hintergrund konvertieren) und sync (inline konvertieren, modernes Format sofort bereitstellen)
  • Thread-Pool-Integration - Konvertierungen laufen in NGINX-Thread-Pools, wodurch die Ereignisschleife frei bleibt
  • Sanfter Rückfall - Konvertierungsfehler, beschädigte Bilder, fehlender Thread-Pool, voller Speicher: immer das Original bereitstellen, niemals 500 zurückgeben
  • Größenschwellen - Bilder unter immerse_min_size oder über immerse_max_size überspringen, um CPU bei winzigen Icons oder riesigen Assets nicht zu verschwenden
  • CDN-sicher - fügt Vary: Accept hinzu, damit Caches und CDNs nicht das falsche Format an den falschen Client ausliefern
  • Debug-Header - X-Immerse: hit|miss|error|pass zeigt an, was passiert ist (umschaltbar)
  • Magische Byte-Erkennung - identifiziert das Eingabeformat anhand der Dateisignatur, nicht der URL-Erweiterung

Konfiguration

Minimales Beispiel

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

Mit proxied Inhalten

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

Vollständiges Beispiel mit allen Direktiven

thread_pool immerse threads=4;

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

    # Standardwerte für alle Standorte
    immerse_formats avif webp;
    immerse_webp_quality 82;
    immerse_avif_quality 63;

    server {
        listen 80;

        # Statische Bilder - lazy-Modus (Standard)
        location /images/ {
            immerse on;
            immerse_thread_pool immerse;
            immerse_min_size 2k;
            immerse_max_size 5m;
            alias /var/www/images/;
        }

        # Proxied Bilder - sync-Modus für sofortige Konvertierung
        location /api/photos/ {
            immerse on;
            immerse_mode sync;
            immerse_thread_pool immerse;
            proxy_pass http://backend;
        }

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

        # Debug-Header in der Produktion deaktivieren
        location /cdn/ {
            immerse on;
            immerse_x_header off;
            immerse_thread_pool immerse;
            alias /var/www/cdn/;
        }
    }
}

Direktivenreferenz

immerse

Syntax: immerse on | off;
Standard: off
Kontext: location

Aktiviert oder deaktiviert die Konvertierung von Bildformaten für den Standort. Wenn aktiviert, interceptiert das Modul Bildantworten und versucht eine Konvertierung basierend auf der Unterstützung des Clients.

Erfordert, dass immerse_cache_path auf der http-Ebene gesetzt ist. Wenn der Cache-Pfad nicht konfiguriert ist, protokolliert das Modul einen Fehler und leitet die Antwort unverändert weiter.

immerse_cache_path

Syntax: immerse_cache_path path [levels=levels] [max_size=size] [inactive=time];
Standard: keine (erforderlich, wenn immerse aktiviert ist)
Kontext: http

Setzt das Cache-Verzeichnis und die Parameter. Diese Direktive ist erforderlich - das Modul wird keine Bilder konvertieren, ohne einen konfigurierten Cache-Pfad.

Parameter:

  • path - Verzeichnis im Dateisystem für zwischengespeicherte Konvertierungen. Wird automatisch erstellt, wenn es nicht existiert.
  • levels - Tiefe der Unterverzeichnishierarchie, angegeben als durch Doppelpunkt getrennte Ziffern (1 oder 2). Standard: 1:2. Mit levels=1:2 wird ein Cache-Schlüssel a3b1c4d5e6... unter path/a/3b/a3b1c4d5e6....webp gespeichert.
  • max_size - maximale Gesamtgröße des Caches. Akzeptiert Größen-Suffixe (k, m, g). Standard: nicht gesetzt (kein Limit).
  • inactive - Zeit, nach der ungenutzte zwischengespeicherte Dateien zur Entfernung berechtigt sind. Akzeptiert Zeit-Suffixe (s, m, h, d). Standard: 30d.
immerse_cache_path /var/cache/nginx/immerse levels=1:2 max_size=1g inactive=30d;

immerse_formats

Syntax: immerse_formats format ...;
Standard: avif webp
Kontext: http, server, location

Setzt die bevorzugten Ausgabeformate in Prioritätsreihenfolge. Wenn mehrere Formate vom Client mit gleichen Qualitätsfaktoren akzeptiert werden, gewinnt das erste hier aufgeführte Format.

Gültige Formate: avif, webp. Mindestens eines muss zur Kompilierungszeit unterstützt werden.

# Bevorzuge WebP über AVIF
immerse_formats webp avif;

# Nur WebP
immerse_formats webp;

immerse_mode

Syntax: immerse_mode lazy | sync;
Standard: lazy
Kontext: location

Setzt die Konvertierungsstrategie für Cache-Fehlgeschlagen.

lazy - stellt das Originalbild sofort ohne Latenzüberhang bereit. Wenn die Bildquelle dateibasiert ist, wird eine Hintergrundkonvertierung im Thread-Pool in die Warteschlange gestellt. Die konvertierte Variante ist für nachfolgende Anfragen verfügbar. Am besten für die Bereitstellung statischer Dateien, bei denen die Latenz der ersten Anfrage wichtig ist.

sync - puffert den gesamten Antwortkörper, konvertiert ihn im Thread-Pool und stellt das konvertierte Bild in derselben Anfrage bereit. Der Worker wird nicht blockiert (der Thread-Pool übernimmt die Arbeit). Am besten für proxied Inhalte oder wenn Sie möchten, dass jede Antwort in einem modernen Format vorliegt.

# Statische Dateien - lazy ist in Ordnung, Cache wird schnell warm
location /images/ {
    immerse on;
    immerse_mode lazy;
}

# API-Antworten - sync stellt sicher, dass das moderne Format bei der ersten Anfrage vorliegt
location /api/photos/ {
    immerse on;
    immerse_mode sync;
    proxy_pass http://backend;
}

immerse_webp_quality

Syntax: immerse_webp_quality quality;
Standard: 80
Kontext: http, server, location

WebP Kodierungsqualität (1-100). Höhere Werte erzeugen eine bessere visuelle Qualität bei größeren Dateigrößen. Werte um 75-85 bieten ein gutes Gleichgewicht für die meisten Inhalte.

immerse_avif_quality

Syntax: immerse_avif_quality quality;
Standard: 60
Kontext: http, server, location

AVIF Kodierungsqualität (1-100). AVIF erreicht eine gute visuelle Qualität bei niedrigeren numerischen Werten als WebP oder JPEG. Werte um 50-70 sind typisch für die Webbereitstellung. Der Encoder verwendet Geschwindigkeit 6 (ausgewogenes Verhältnis von Geschwindigkeit/Qualität).

immerse_min_size

Syntax: immerse_min_size size;
Standard: 1k (1024 Bytes)
Kontext: http, server, location

Mindestgröße des Antwortkörpers für die Konvertierung. Bilder kleiner als dies werden unverändert weitergeleitet. Dies vermeidet, CPU bei winzigen Bildern (Favicon, 1x1 Tracking-Pixel) zu verschwenden, bei denen die Formatkonvertierung vernachlässigbare Einsparungen bringt.

immerse_max_size

Syntax: immerse_max_size size;
Standard: 10m (10485760 Bytes)
Kontext: http, server, location

Maximale Größe des Antwortkörpers für die Konvertierung. Bilder größer als dies werden unverändert weitergeleitet. Dies verhindert Ressourcenerschöpfung durch sehr große Bilder, die während der Dekodierung/Kodierung signifikanten Speicher und CPU verbrauchen würden.

immerse_thread_pool

Syntax: immerse_thread_pool name;
Standard: default
Kontext: http, server, location

Name des NGINX Thread-Pools, der für Konvertierungsaufgaben verwendet werden soll. Muss mit einer thread_pool-Direktive im Hauptkontext übereinstimmen.

# Definiere einen dedizierten Pool
thread_pool immerse threads=4;

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

Größenrichtlinien: Beginnen Sie mit der Anzahl der CPU-Kerne. Die Bildkodierung ist CPU-gebunden, sodass mehr Threads als Kerne keinen Vorteil bieten. Wenn derselbe Server andere Thread-Pool-Arbeiten (aio) behandelt, ziehen Sie einen dedizierten Pool für immerse in Betracht.

immerse_x_header

Syntax: immerse_x_header on | off;
Standard: on
Kontext: http, server, location

Steuert den X-Immerse Antwortheader. Wenn aktiviert, enthält jede verarbeitete Antwort einen Header, der angibt, was passiert ist:

Wert Bedeutung
hit Aus dem Cache bereitgestellt
miss Cache-Fehlgeschlagen; konvertiert (sync) oder Original bereitgestellt (lazy)
error Konvertierung fehlgeschlagen; Original als Rückfall bereitgestellt

Deaktivieren Sie dies in der Produktion, wenn Sie nicht den internen Modulstatus an die Clients weitergeben möchten.

Antwortheader

Wenn ngx_immerse eine Antwort verarbeitet, ändert oder fügt es die folgenden Header hinzu:

Header Wert Wann
Content-Type image/webp oder image/avif Konvertiert oder aus dem Cache bereitgestellt
Content-Length Größe des konvertierten Bildes Konvertiert oder aus dem Cache bereitgestellt
Vary Accept Immer (auch bei Durchleitung), wenn das Modul aktiv ist
X-Immerse hit, miss oder error Wenn immerse_x_header aktiviert ist

Der Vary: Accept Header ist entscheidend für das korrekte Verhalten von CDNs. Ohne ihn kann ein CDN eine WebP-Antwort zwischenspeichern und sie an einen Client ausliefern, der nur JPEG unterstützt.

Analyse des Accept-Headers

Das Modul analysiert den Accept-Anforderungsheader gemäß RFC 7231:

  • Extrahiert image/webp und image/avif Einträge mit ihren Qualitätsfaktoren
  • q=0 bedeutet, dass der Client dieses Format ausdrücklich ablehnt
  • q=1 (oder kein q-Parameter) bedeutet volle Unterstützung
  • Das Format mit dem höchsten q-Wert wird ausgewählt
  • Bei gleichem q gewinnt das erste Format in immerse_formats
  • Wenn keines der Formate vorhanden ist oder beide q=0 haben, wird das Original bereitgestellt

Beispiele:

Accept-Header Ergebnis (mit Standard immerse_formats avif webp)
image/avif, image/webp AVIF (erstes in der Konfiguration, gleiches q)
image/webp WebP
image/avif;q=0.8, image/webp;q=0.9 WebP (höheres q)
image/avif;q=0, image/webp WebP (AVIF abgelehnt)
text/html, image/jpeg Original (kein modernes Format)

Eingabeformat-Erkennung

Quellbilder werden anhand magischer Bytes im Antwortkörper identifiziert, nicht anhand der Dateierweiterung:

Format Magische Bytes
JPEG FF D8 FF
PNG 89 50 4E 47 0D 0A 1A 0A
GIF GIF87a oder GIF89a

Bilder, die bereits im WebP- oder AVIF-Format vorliegen, werden unverändert weitergeleitet. Animierte GIFs (mehrere Frames) werden ebenfalls unverändert weitergeleitet.

Cache

Funktionsweise

Der Cache speichert konvertierte Bilder in einer Verzeichnisstruktur, die nach MD5-Hash verschlüsselt ist. Der Hash-Eingang ist URI + source_mtime + target_format + quality, sodass:

  • Verschiedene Formate (WebP, AVIF) erhalten separate Cache-Einträge
  • Änderungen der Qualitätseinstellungen erzeugen neue Cache-Einträge
  • Änderungen am Originalbild (Änderung seiner mtime) machen die zwischengespeicherte Konvertierung automatisch ungültig

Verzeichnislayout

Mit levels=1:2 produziert ein Cache-Schlüssel a3b1c4d5...:

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

Atomare Schreibvorgänge

Cache-Dateien werden atomar geschrieben: Daten gehen zuerst in eine temporäre Datei, dann verschiebt rename() sie an ihren Platz. Dies verhindert, dass teilweise geschriebene Dateien unter gleichzeitiger Last bereitgestellt werden.

Cache-Warmup

Im lazy-Modus stellt die erste Anfrage für ein Bild das Original bereit. Die Konvertierung läuft im Hintergrund, und nachfolgende Anfragen erhalten das zwischengespeicherte moderne Format. Im sync-Modus löst die allererste Anfrage die Konvertierung aus und stellt das Ergebnis bereit.

Manuelles Leeren des Caches

Um den gesamten Cache zu leeren:

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

Ein NGINX-Neuladen ist nicht erforderlich. Das Modul wird die Verzeichnisse nach Bedarf neu erstellen.

Fehlerbehandlung

ngx_immerse folgt einer strengen "nie brechen, was bereits funktioniert"-Politik:

Bedingung Verhalten
Konvertierung schlägt fehl (Codec-Fehler) Original bereitstellen, Fehler protokollieren
Cache-Schreiben schlägt fehl (Speicher voll, Berechtigungen) Aus dem Speicher konvertiert bereitstellen, Warnung protokollieren
Beschädigtes oder abgeschnittenes Eingabebild Original bereitstellen, Fehler protokollieren
Bild unter min_size oder über max_size Unverändert weiterleiten
Kein modernes Format im Client Accept Unverändert weiterleiten, Vary: Accept hinzufügen
Thread-Pool nicht gefunden Rückfall auf synchrone (blockierende) Konvertierung
immerse_cache_path nicht konfiguriert Unverändert weiterleiten, Fehler protokollieren
Unbekanntes Bildformat (nicht JPEG/PNG/GIF) Unverändert weiterleiten

Das Modul wird niemals einen 500-Fehler aufgrund eines Konvertierungsfehlers zurückgeben.

Architektur

Quelldateien

Datei Zweck
config NGINX-Bausystemintegration, Bibliotheksdetektion
src/ngx_http_immerse_common.h Gemeinsame Typen, Konstanten, Funktionsdeklarationen
src/ngx_http_immerse_module.c Modul-Einstiegspunkt, Direktiven, Konfigurationslebenszyklus
src/ngx_http_immerse_filter.c Header- und Body-Filterkette, Zustandsmaschine, Thread-Pool-Dispatch
src/ngx_http_immerse_convert.c Bilddekodierungs-/Kodierungs-Engine (läuft im Thread-Pool)
src/ngx_http_immerse_cache.c Cache-Schlüsselgenerierung, Lookup, atomare Speicherung
src/ngx_http_immerse_accept.c RFC 7231 Accept-Header-Parser
src/ngx_http_immerse_util.c Antwortversand, Body-Pufferung, Format-Erkennung, Header-Hilfsfunktionen

Thread-Sicherheit

Alle Arbeiten zur Bildkonvertierung laufen in NGINX-Thread-Pool-Workern. Der Konvertierungscode (ngx_http_immerse_convert.c) verwendet nur malloc/free, POSIX-Datei-I/O und Aufrufe der Bildbibliothek. Er greift niemals auf den gemeinsamen Zustand von NGINX, Anfrage-Pools oder die Ereignisschleife zu.

Die Ergebnisse werden über den Standard-NGINX-Thread-Aufgabenabschlussmechanismus (ngx_thread_task_t) an die Haupt-Ereignisschleife zurückgegeben.

Zustandsmaschine

Der Body-Filter verwendet eine phasenbasierte Zustandsmaschine:

START -> READ -> CONVERT -> SEND -> DONE     (sync-Modus)
PASS -> DONE                                  (lazy-Modus, erste Anfrage)
SERVE_CACHE -> DONE                           (Cache-Treffer)

Tests

Docker-basiert (empfohlen)

# Alle Tests ausführen (HUP-Modus für ~10x schnellere Iteration)
make tests

# Eine bestimmte Testdatei ausführen
make tests T=t/sync.t

# Ohne HUP-Modus ausführen (sauberer Zustand zwischen den Tests)
make tests HUP=0

# Interaktive Shell zum Debuggen
make shell

# Basisbild neu erstellen (nach Änderungen an Dockerfile)
make base-image

# Gegen eine andere NGINX-Version testen
make tests NGINX_VERSION=release-1.26.2

CI

GitHub Actions führt Tests gegen NGINX 1.26.2, 1.27.3 und 1.28.0 bei jedem Push und Pull-Request aus.

Test-Suite

Datei Abdeckung
t/accept.t Analyse des Accept-Headers, q-Werte, Format-Auswahl
t/sync.t Sync-Modus-Konvertierung für JPEG, PNG, GIF in WebP/AVIF
t/lazy.t Lazy-Modus: Original zuerst bereitgestellt, Cache danach befüllt
t/cache.t Cache Treffer/Fehlgeschlagen Verhalten
t/limits.t Filterung von min_size und max_size
t/fallback.t Rückfall bei beschädigten Bildern
t/passthrough.t Modul deaktiviert, Nicht-Bild-Inhalt, kein Accept-Header
t/config.t Direktivenvalidierung, x_header Umschaltung
t/vary.t Anwesenheit des Vary: Accept Headers

Debugging

  • Überprüfen Sie test-error.log im Repo-Stammverzeichnis für NGINX-Debug-Ausgaben
  • Verwenden Sie make shell, um in den Container zu gelangen und Tests manuell auszuführen
  • Der Log-Level ist im Docker-Testumfeld auf debug gesetzt