Nginx FastCGI Cache einrichten 2026 – Performance-Guide

Was ist der Nginx FastCGI Cache und wann lohnt er sich?

Der FastCGI Cache ist ein in Nginx integrierter Vollseiten-Cache, der die komplette HTML-Ausgabe von PHP-FPM-Anwendungen auf der Festplatte zwischenspeichert. Statt bei jedem Request WordPress, Joomla oder Laravel komplett zu booten – Datenbankabfragen, Plugin-Loading, Template-Rendering – liefert Nginx die fertige HTML-Seite direkt aus dem Cache aus. In der Praxis bedeutet das: statt 250–800 ms Antwortzeit sind 5–20 ms möglich, und die CPU-Last sinkt um 70–90 %.

Der entscheidende Vorteil gegenüber Redis Object Cache oder APCu: Der FastCGI Cache greift vor PHP. Es wird kein PHP-FPM-Worker belegt, keine MySQL-Verbindung geöffnet, kein Autoloader gestartet. Bei einem typischen WordPress mit 15 aktiven Plugins sparen Sie pro gecachtem Request rund 60–120 MB Speicher-Allokation und 40–90 ms CPU-Zeit.

Lohnenswert ist der FastCGI Cache ab etwa 5.000 Seitenaufrufen pro Tag oder wenn Sie mit einem kleinen VPS (1 vCPU, 2 GB RAM, ca. 5–8 €/Monat) Traffic-Spitzen abfangen wollen. Für rein statische Sites ist er überflüssig, für Shops mit Warenkorb-Logik nur mit sorgfältigem Bypass-Setup.

Ein Nachteil: Der Cache liegt auf der Platte, nicht im RAM. Bei NVMe-SSDs (bei Hetzner, Netcup und Contabo seit 2024 Standard) liegt die Latenz bei 0,1–0,3 ms pro Lookup – vernachlässigbar. Bei alten SATA-SSDs oder gar HDDs würde ich stattdessen auf Varnish oder Redis setzen.

Voraussetzungen und Server-Setup 2026

Sie brauchen Nginx 1.24 oder neuer (empfohlen: 1.26/1.28 stable), PHP-FPM 8.2+ und ausreichend Plattenplatz. Der Cache-Ordner sollte auf einer eigenen Partition oder mindestens einem eigenen Verzeichnis mit noatime-Mount-Option liegen, damit das Lesen keine Schreibzugriffe triggert.

Als Richtwert für die Cache-Größe gilt: 1 GB Cache pro 10.000 gecachte Seiten bei durchschnittlich 100 KB HTML pro Seite. Bei einem Magazin mit 50.000 Artikeln planen Sie 5–8 GB ein. Prüfen Sie freien Platz mit df -h /var/cache/nginx.

Die RAM-Anforderung hängt von keys_zone ab: Pro 1 MB Shared Memory lassen sich etwa 8.000 Cache-Keys verwalten. Für 100.000 Seiten reichen also 16 MB. Das ist der häufigste Anfängerfehler – keys_zone wird mit der Cache-Größe verwechselt.

Installation auf Debian 13 / Ubuntu 24.04:

apt update
apt install nginx-full php8.3-fpm -y
nginx -V 2>&1 | grep -o 'with-http_fastcgi_module'
# Ausgabe: with-http_fastcgi_module
mkdir -p /var/cache/nginx/fastcgi
chown -R www-data:www-data /var/cache/nginx

FastCGI Cache in nginx.conf aktivieren

Die globale Cache-Definition gehört in den http-Block, typischerweise in /etc/nginx/nginx.conf oder eine separate Datei unter /etc/nginx/conf.d/cache.conf. Wichtig: use_temp_path=off vermeidet einen unnötigen Kopiervorgang zwischen zwei Verzeichnissen und spart bei hoher Last bis zu 15 % I/O.

fastcgi_cache_path /var/cache/nginx/fastcgi
                   levels=1:2
                   keys_zone=FASTCGICACHE:100m
                   max_size=10g
                   inactive=60m
                   use_temp_path=off;

fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header updating
                        http_500 http_503;
fastcgi_cache_background_update on;
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 5s;
fastcgi_cache_min_uses 2;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_valid 404 1m;

levels=1:2 erzeugt eine zweistufige Verzeichnishierarchie (z. B. /a/3b/…). Das verhindert, dass ein einzelnes Verzeichnis mit Hunderttausenden Dateien zum Flaschenhals wird – auf ext4 ein echtes Problem, auf XFS weniger. inactive=60m bedeutet: Seiten, die 60 Minuten nicht abgerufen wurden, werden beim nächsten Platzmangel zuerst gelöscht.

fastcgi_cache_lock on ist der wichtigste Parameter für Stabilität: Wenn 50 gleichzeitige Requests eine noch nicht gecachte Seite anfordern, rendert nur ein Request PHP, die anderen 49 warten auf das Ergebnis. Ohne Lock entsteht bei Traffic-Spikes ein „Cache Stampede", der Ihre PHP-FPM-Worker in Sekunden ausschöpft.

Nach der Änderung immer testen und neu laden:

nginx -t
systemctl reload nginx

Cache-Key richtig definieren

Der Cache-Key bestimmt, welche Requests als identisch gelten. Ein falscher Key ist die häufigste Ursache für „Warum sehe ich die Seite von jemand anderem?"-Tickets. Die Standardformel $scheme$request_method$host$request_uri ist für die meisten Setups korrekt, hat aber drei Fallstricke.

Erstens: $request_uri enthält Query-Strings. Damit wird /seite/?utm_source=newsletter separat gecacht von /seite/. Bei Marketing-Kampagnen mit vielen UTM-Parametern explodiert der Cache. Lösung: In WordPress den utm_*-Parameter vorher strippen oder $uri statt $request_uri verwenden, wenn Sie keine Parameter-abhängigen Seiten haben.

Zweitens: Mobile und Desktop. Wenn Sie kein separates Mobile-Theme ausliefern, ignorieren Sie das. Andernfalls ergänzen Sie $http_user_agent – aber vorsichtig, das fragmentiert den Cache stark.

Drittens: Multi-Domain-Setups. $host im Key sorgt dafür, dass shop.example.com und www.example.com getrennte Cache-Einträge bekommen. Das ist gewünscht, kostet aber doppelten Speicher.

Cache-Key-VarianteEinsatzCache-Größe
$scheme$request_method$host$request_uriStandard, WordPress ohne UTM1x
$scheme$request_method$host$uriParameter ignorieren0,3x
+ $http_user_agentSeparates Mobile-Theme2–4x
+ $geoip_country_codeLänderspezifische Inhalte5–20x

Faustregel: Jede Variable im Cache-Key multipliziert die Anzahl der Cache-Einträge. Halten Sie den Key so kurz wie möglich und so lang wie nötig.

Cache-Regeln für WordPress, WooCommerce und PHP-Apps

In der Server-Block-Datei (/etc/nginx/sites-available/example.com) definieren Sie, welche Requests überhaupt gecacht werden dürfen. Beginnen Sie mit einer Variable $skip_cache, die per map oder if gesetzt wird.

server {
    listen 443 ssl http2;
    server_name example.com;
    root /var/www/example.com;
    index index.php;

    set $skip_cache 0;

    if ($request_method = POST) { set $skip_cache 1; }
    if ($query_string != "") { set $skip_cache 1; }
    if ($request_uri ~* "/wp-admin/|/wp-login.php|/cart/|/checkout/|/my-account/") {
        set $skip_cache 1;
    }
    if ($http_cookie ~* "wordpress_logged_in|comment_author|woocommerce_items_in_cart|wp_woocommerce_session") {
        set $skip_cache 1;
    }

    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        fastcgi_cache FASTCGICACHE;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;
        fastcgi_cache_valid 200 301 302 60m;
        fastcgi_cache_valid 404 1m;

        add_header X-FastCGI-Cache $upstream_cache_status always;
    }
}

Der Unterschied zwischen fastcgi_cache_bypass und fastcgi_no_cache ist wichtig: Ersteres liest den Cache nicht (liefert also immer frisch), Letzteres schreibt das Ergebnis nicht in den Cache. Für $skip_cache brauchen Sie beides – sonst landet eine eingeloggte Admin-Ansicht im Cache und wird Gästen ausgeliefert.

Für WooCommerce würde ich zusätzlich /warenkorb/, /kasse/ und alle URLs mit ?add-to-cart= ausschließen. Produktkategorien und einzelne Produktseiten lassen sich dagegen problemlos cachen – sie sind für alle Besucher identisch, solange der Warenkorb-Status über Cookies läuft.

Bei Laravel oder Symfony heißt der Cache-Key oft anders, weil die App über index.php mit Query-String routet. Dort funktioniert $uri nicht – verwenden Sie $request_uri und schließen Sie /api/ und /admin/ explizit aus.

Cache-Bypass: Cookies, Login und personalisierte Inhalte

Der gefährlichste Fehler beim FastCGI Cache ist das Ausliefern personalisierter Inhalte an falsche Nutzer. Nginx prüft standardmäßig Set-Cookie im Response-Header und cacht solche Antworten nicht – das ist ein Sicherheitsnetz, aber kein Ersatz für explizite Regeln.

Wenn Ihre Anwendung Set-Cookie bei jedem Request sendet (viele WordPress-Setups mit Cookie-Consent-Plugins tun das), wird der Cache nie gefüllt. In diesem Fall müssen Sie fastcgi_ignore_headers Set-Cookie setzen – aber nur, wenn Sie sicher sind, dass das Cookie keine personalisierten Inhalte steuert. Prüfen Sie das vorher mit:

curl -sI https://example.com/ | grep -i set-cookie

Ein zweites Sicherheitsnetz ist fastcgi_ignore_headers Cache-Control Expires Vary. Damit überschreiben Sie die Cache-Header der Anwendung. Sinnvoll, wenn WordPress per Plugin Cache-Control: no-cache, must-revalidate sendet und Sie das bewusst aushebeln wollen.

Für DSGVO-Konformität gilt: Der Cache darf keine personenbezogenen Daten enthalten. Kontrollieren Sie regelmäßig mit einem Grep über den Cache-Ordner, ob E-Mail-Adressen oder Session-IDs im HTML landen:

grep -rl "@" /var/cache/nginx/fastcgi | head -20

Wenn Sie Treffer sehen, ist Ihr Bypass-Setup lückenhaft. Typische Ursachen sind fehlende Cookie-Ausschlüsse oder Kommentarformulare, deren Bestätigungsseite gecacht wird.

Cache-Purging und Invalidierung automatisieren

Ein Cache ohne Invalidierung ist ein Bug-Generator: Sie veröffentlichen einen Artikel und Besucher sehen 60 Minuten lang die alte Version. Es gibt drei Wege, das zu lösen.

Weg 1 – Zeitbasiert (einfach): fastcgi_cache_valid 200 10m. Nach 10 Minuten wird neu gerendert. Für Blogs mit seltenen Updates völlig ausreichend, aber bei News-Seiten zu träge.

Weg 2 – Purge-Endpoint (empfohlen): Das Modul ngx_cache_purge (Paket libnginx-mod-http-cache-purge) erlaubt gezieltes Löschen einzelner URLs.

apt install libnginx-mod-http-cache-purge -y

# im server-Block:
location ~ /purge(/.*) {
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    deny all;
    fastcgi_cache_purge FASTCGICACHE "$scheme$request_method$host$1";
}

Nach jedem WordPress-Post-Publish feuert Ihr Theme oder ein Snippet einen Request:

curl -s "http://127.0.0.1/purge/permalalink-slug/"

Weg 3 – Kompletter Flush: Bei größeren Änderungen (Theme-Update, Plugin-Aktivierung) löschen Sie alles:

rm -rf /var/cache/nginx/fastcgi/*
systemctl reload nginx

Wichtig: Löschen Sie das Verzeichnis nicht selbst, nur den Inhalt. Nginx legt die Struktur beim nächsten Request neu an, aber ein fehlendes Root-Verzeichnis führt zu Permission denied-Fehlern im Log. Binden Sie den Flush in Ihre Deployment-Pipeline ein – nach jedem git pull und composer install sollte ein Cache-Flush folgen.

Monitoring: Hit-Rate messen und Benchmarks

Die Cache-Hit-Rate ist Ihre wichtigste Kennzahl. Alles unter 70 % deutet auf ein Bypass-Problem hin. Nginx liefert den Status über die Variable $upstream_cache_status mit den Werten HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING und REVALIDATED.

Definieren Sie ein eigenes Log-Format im http-Block:

log_format cache '$remote_addr - $upstream_cache_status [$time_local] '
                 '"$request" $status $body_bytes_sent $request_time';

access_log /var/log/nginx/cache.log cache;

Auswertung der Hit-Rate (Feld 3 ist der Cache-Status):

awk '{print $3}' /var/log/nginx/cache.log | sort | uniq -c | sort -rn

# Beispielausgabe:
#  84213 HIT
#  11204 MISS
#   3187 BYPASS
#    412 EXPIRED

In diesem Beispiel liegt die Hit-Rate bei 84,5 % – ein guter Wert. Ein hoher BYPASS-Anteil ist normal, wenn Sie viel Admin-Traffic haben. Ein hoher EXPIRED-Anteil bedeutet, dass Ihre TTL zu kurz ist.

Benchmark mit wrk auf einem 2-vCPU-VPS (Hetzner CX22, PHP 8.3, WordPress 6.8, 22 Plugins):

SzenarioRequests/sLatenz p99CPU-Last
Ohne Cache1781.240 ms98 %
Redis Object Cache640310 ms74 %
FastCGI Cache (MISS)1951.180 ms96 %
FastCGI Cache (HIT)4.85034 ms11 %

Der Sprung von 178 auf 4.850 Requests/s entspricht Faktor 27. Wichtig: Diese Werte gelten für gecachte Seiten. Der erste Request nach Ablauf der TTL ist immer langsam – deshalb ist fastcgi_cache_background_update on so wertvoll: Ein Besucher bekommt die alte (STALE) Version, während im Hintergrund neu gerendert wird.

Typische Fehler und Troubleshooting

Cache füllt sich nicht: Prüfen Sie mit curl -I https://example.com/, ob X-FastCGI-Cache: MISS und beim zweiten Aufruf HIT erscheint. Bleibt es bei MISS, sendet PHP einen Set-Cookie-Header oder Cache-Control: no-cache. Log-Check: tail -f /var/log/nginx/error.log.

502 Bad Gateway nach Cache-Aktivierung: Meist ein Rechte-Problem. Nginx läuft als www-data, der Cache-Ordner gehört aber root. Lösung: chown -R www-data:www-data /var/cache/nginx und SELinux-Kontext auf RHEL-basierten Systemen mit restorecon -Rv /var/cache/nginx setzen.

Alte Inhalte nach Update: TTL zu lang oder Purge-Endpoint nicht erreichbar. Testen Sie curl -v "http://127.0.0.1/purge/testseite/" – bei Erfolg antwortet Nginx mit 200 Successful purge.

Cache wächst unkontrolliert: max_size vergessen oder inactive zu hoch. Nginx räumt den Cache nur beim Schreiben neuer Einträge auf – ein voller Cache wird also nie von selbst geleert, wenn kein Traffic kommt.

Sicherheit und DSGVO beim FastCGI Cache

Der Cache-Ordner darf niemals über HTTP erreichbar sein. Prüfen Sie, dass /var/cache/nginx außerhalb des root-Verzeichnisses liegt und keine location darauf zeigt. Ein versehentlich ausgeliefertes Cache-Verzeichnis gibt Angreifern Einblick in alle gecachten Seiten inklusive eventueller Fehlermeldungen.

Aus DSGVO-Sicht gilt: Gecachte Seiten sind Kopien Ihrer Inhalte. Wenn Sie Formulare, Session-Daten oder personalisierte Empfehlungen ausliefern, müssen diese zwingend vom Cache ausgeschlossen sein. Dokumentieren Sie Ihr Bypass-Setup im Verarbeitungsverzeichnis – das ist bei Audits ein häufiger Prüfpunkt.

Setzen Sie außerdem fastcgi_hide_header X-Powered-By und fastcgi_hide_header X-Pingback, um Versionsinformationen nicht in gecachten Antworten zu verteilen. Bei WordPress kommt oft noch Link: <https://example.com/wp-json/>; rel="https://api.w.org/" dazu – harmlos, aber für Angreifer ein Hinweis auf REST-API-Endpunkte.

Ein letzter Punkt: Cache-Poisoning. Wenn Ihre Anwendung Header wie X-Forwarded-Host ungefiltert in die Ausgabe schreibt und diese in den Cache gelangen, können Angreifer manipulierte Inhalte für alle Besucher platzieren. Prüfen Sie nach jedem Deployment mit curl -H "X-Forwarded-Host: evil.com" https://example.com/, ob Ihre Domain in der Antwort auftaucht.

FastCGI Cache vs. Redis vs. Varnish im Vergleich

Alle drei lösen unterschiedliche Probleme. Der FastCGI Cache ist ein Vollseiten-Cache auf Dateiebene, Redis Object Cache speichert einzelne Datenbankabfragen und Objekte im RAM, Varnish ist ein vorgeschalteter HTTP-Beschleuniger mit eigener Konfigurationssprache (VCL).

KriteriumFastCGI CacheRedis Object CacheVarnish
Cache-EbeneVor PHP, ganze SeiteIn PHP, Objekte/QueriesVor Nginx, ganze Seite
SpeicherortNVMe-SSDRAMRAM
Speedup (HIT)20–30x3–5x25–40x
Setup-AufwandNiedrig (30 Min.)Niedrig (15 Min.)Hoch (2–4 Std.)
RAM-Bedarf~100 MB256 MB–2 GB1–4 GB
SSL-TerminierungJa (nativ)N/ANein (Nginx davor)
PurgingURL-genauKey-genauTag-basiert (BAN)

Die beste Kombination für WordPress 2026 ist FastCGI Cache plus Redis Object Cache: Der FastCGI Cache liefert die fertige Seite für Gäste, Redis beschleunigt die Requests, die den Cache umgehen (eingeloggte Nutzer, Warenkorb, Admin). Der zusätzliche RAM-Bedarf von 256 MB kostet bei Hetzner rund 1,50 €/Monat – bei einem Shop mit 50.000 Besuchern pro Monat ist das die günstigste Performance-Investition überhaupt.

Varnish lohnt sich erst ab etwa 1 Mio. Requests pro Tag oder wenn Sie Edge-Side-Includes, komplexe Header-Manipulation oder ESI-Fragmente benötigen. Für 95 % aller WordPress-, Shopware- und Laravel-Projekte ist der FastCGI Cache die pragmatischere Wahl: weniger bewegliche Teile, native TLS-Unterstützung, keine separate Konfigurationssprache.

Für maximale Performance kombinieren Sie den Cache mit HTTP/2, Brotli-Kompression (ngx_brotli) und einem open_file_cache max=10000 inactive=30s. Letzteres spart bei statischen Assets weitere 5–10 % Systemzeit.

FAQ

Wie viel RAM braucht der Nginx FastCGI Cache?

Der Shared-Memory-Bereich (keys_zone) benötigt rund 1 MB pro 8.000 gecachte URLs. Für 100.000 Seiten reichen 16 MB, für die meisten Sites sind 100 MB großzügig dimensioniert. Der eigentliche HTML-Inhalt liegt auf der SSD und belegt dort je nach max_size zwischen 1 und 20 GB. RAM und Cache-Größe sind also zwei völlig getrennte Werte.

Warum wird meine WordPress-Seite nicht gecacht?

In 90 % der Fälle sendet PHP einen Set-Cookie-Header, den Nginx als Personalisierung interpretiert. Prüfen Sie mit curl -sI https://example.com/ | grep -i set-cookie. Ist ein Cookie dabei, müssen Sie entweder das verursachende Plugin deaktivieren oder bewusst fastcgi_ignore_headers Set-Cookie setzen – Letzteres nur, wenn das Cookie keine nutzerspezifischen Inhalte steuert.

Kann ich den FastCGI Cache für WooCommerce nutzen?

Ja, mit Einschränkungen. Produktseiten, Kategorien und statische Seiten lassen sich problemlos cachen. Warenkorb, Kasse und Mein-Konto-Bereich müssen per Cookie-Ausschluss (woocommerce_items_in_cart, wp_woocommerce_session) vom Cache ausgenommen werden. Bei Shops mit dynamischen Preisen (B2B, Kundengruppen) würde ich auf Object Cache plus OPcache setzen und den Vollseiten-Cache nur für Gäste aktivieren.

Wie lange sollte die Cache-TTL sein?

Für Blogs und Unternehmensseiten sind 60 Minuten ein guter Startwert, für News-Portale 5–10 Minuten, für Shops 15–30 Minuten. Nutzen Sie zusätzlich fastcgi_cache_background_update on, damit Besucher auch nach Ablauf der TTL sofort die alte Version bekommen, während im Hintergrund neu gerendert wird. Bei aktivem Purge-Endpoint können Sie die TTL auf 24 Stunden setzen und nur bei Änderungen gezielt löschen.

Funktioniert der FastCGI Cache auch mit HTTP/3?

Ja, der Cache arbeitet protokollunabhängig unterhalb der HTTP-Schicht. Nginx unterstützt HTTP/3 seit Version 1.25 über das ngx_http_v3_module. Achten Sie darauf, dass listen 443 quic reuseport; und der Alt-Svc-Header korrekt gesetzt sind – der Cache-Key bleibt unverändert, da $scheme weiterhin https liefert.