Browser-Caching und Komprimierung für WordPress auf Apache und nginx einrichten
Cache-Control, Expires, gzip und Brotli für WordPress richtig setzen: .htaccess für Apache, server-Block für nginx, passende Cache-Dauer und Prüfung mit curl. Im Labor mit Apache 2.4 und nginx 1.26 getestet.
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Mehr dazu

Jeder Seitenaufruf einer WordPress-Website lädt Stylesheets, Skripte, Schriften und Bilder. Ohne passende HTTP-Header fragt der Browser diese Dateien beim nächsten Besuch erneut beim Server an, und ohne Komprimierung überträgt der Server Textdateien in voller Größe. Beides kostet Ladezeit, vor allem mobil. Diese Anleitung zeigt, wie Sie Browser-Caching und Komprimierung auf Apache und nginx einrichten, ohne dass Besucher nach einem Update veraltete Dateien sehen. Alle Konfigurationen wurden im Labor mit WordPress 7.1.2, Apache 2.4 und nginx 1.26 geprüft, einschließlich der Fehlerfälle.
Voraussetzungen
- WordPress: eine aktuelle Installation, im Labor WordPress 7.1.2 mit PHP 8.4.
- Webserver: Apache 2.4 mit
mod_expires,mod_headersundmod_deflate, optionalmod_brotli. Oder nginx, im Labor 1.26.3 aus Debian. - Zugriff: Rolle Administrator im Backend, SFTP für die
.htaccess. Für die Server-Konfiguration (virtueller Host, nginx) SSH mit Root-Rechten. - Werkzeug:
curlauf Ihrem Rechner (unter Windows 10 und 11 bereits enthalten) oder die Entwicklerwerkzeuge des Browsers, Reiter „Netzwerk“. - Backup: eine Kopie der aktuellen
.htaccessbzw. der nginx-Konfiguration und ein aktuelles Backup der Website. Ein Tippfehler in der.htaccesslegt die gesamte Website mit Fehler 500 lahm.
Schritt 1: Ausgangslage mit curl prüfen
Wählen Sie eine statische Datei, die WordPress immer ausliefert, etwa jQuery aus dem Core, und fragen Sie nur die Header ab:
curl -sI https://www.example.de/wp-includes/js/jquery/jquery.min.js
curl -sI -H "Accept-Encoding: gzip, br" https://www.example.de/wp-includes/js/jquery/jquery.min.js
Im Labor lieferte ein unveränderter Apache aus dem offiziellen WordPress-Image diese Antwort:
HTTP/1.1 200 OK
Last-Modified: Mon, 28 Aug 2023 17:14:23 GMT
ETag: "15601-603fed35e19c0"
Content-Length: 87553
Vary: Accept-Encoding
Content-Type: text/javascript
Es fehlen Cache-Control und Expires. Der Browser darf die Datei zwar zwischenspeichern, muss aber nach eigenem Ermessen erneut nachfragen. ETag und Last-Modified sorgen dann nur dafür, dass der Server mit „304 Not Modified“ antwortet: Es fließen weniger Daten, die Anfrage selbst bleibt. Beim zweiten Aufruf mit Accept-Encoding antwortete der Server bereits mit Content-Encoding: gzip und 30368 statt 87553 Byte. Viele Hoster haben die Komprimierung also schon aktiv, das Caching nicht.
Verifizieren: Sie wissen, ob Ihr Server Cache-Control, Expires und Content-Encoding sendet, und haben die Ausgabe zum späteren Vergleich gespeichert.
Schritt 2: Die richtige Cache-Dauer wählen
Lange Cache-Zeiten sind nur dann unproblematisch, wenn sich die Adresse einer Datei ändert, sobald sich ihr Inhalt ändert. WordPress erledigt das für Dateien, die Themes und Plugins korrekt über wp_enqueue_style() und wp_enqueue_script() einbinden: Sie erhalten einen Parameter ?ver=, im Labor etwa ?ver=7.1.2 für Core-Dateien oder einen Hash wie ?ver=efaa5193bbad9c60ffd1 für Block-Skripte. Nach einem Update ändert sich dieser Wert, der Browser lädt die neue Datei.
| Dateiart | Empfohlene Dauer | Begründung |
|---|---|---|
| CSS, JavaScript | 1 Jahr | Versionsparameter wechselt bei Updates |
| Bilder (WebP, AVIF, JPEG, PNG, SVG) | 1 Jahr | WordPress hängt bei gleichem Dateinamen ein Suffix wie -1 an |
| Schriften (WOFF2) | 1 Jahr | ändern sich praktisch nie |
| HTML-Seiten | kein Browser-Cache | Inhalte, Warenkorb und Login-Zustand müssen aktuell sein |
Der Trade-off: Bindet ein Theme eine Datei ohne Versionsparameter ein oder überschreiben Sie eine Bilddatei per SFTP unter gleichem Namen, sehen wiederkehrende Besucher bis zu einem Jahr die alte Fassung. Prüfen Sie im Quelltext Ihrer Startseite, ob alle .css- und .js-Adressen ein ?ver= tragen. Bei fehlenden Parametern wählen Sie für CSS und JavaScript lieber eine Woche.
Verifizieren: Im Quelltext der Startseite tragen alle eingebundenen Stylesheets und Skripte einen Versionsparameter, oder Sie haben die Cache-Dauer entsprechend verkürzt.
Schritt 3: Browser-Caching auf Apache per .htaccess
Öffnen Sie per SFTP die .htaccess im WordPress-Hauptverzeichnis und sichern Sie eine Kopie. Fügen Sie den folgenden Block oberhalb von # BEGIN WordPress ein. Den Bereich zwischen # BEGIN WordPress und # END WordPress schreibt WordPress beim Speichern der Permalinks neu, eigene Regeln darin gehen verloren.
# BEGIN Browser-Caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType text/javascript "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/avif "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|webp|avif|jpe?g|png|svg|woff2)$">
Header set Cache-Control "public, max-age=31536000"
</FilesMatch>
</IfModule>
# END Browser-Caching
mod_expires setzt sowohl Expires als auch Cache-Control: max-age. Der zweite Block ergänzt public, damit auch zwischengeschaltete Caches die Dateien speichern dürfen. Für HTML legen Sie bewusst keine Regel an: WordPress-Seiten haben den Typ text/html und bleiben so ohne Ablaufzeit. Die <IfModule>-Klammern sind kein Beiwerk. Im Labor führte eine Header-Zeile ohne Klammer bei abgeschaltetem mod_headers sofort zu Fehler 500 mit dem Eintrag:
/var/www/html/.htaccess: Invalid command 'Header', perhaps misspelled or defined by a module not included in the server configuration
Verifizieren: curl -sI auf die jQuery-Datei zeigt jetzt Cache-Control: public, max-age=31536000 und ein Expires-Datum ein Jahr in der Zukunft. Im Labor ergab das dieselben Header für eine WebP-Datei des Themes. Die Startseite liefert weiter Status 200 und kein Expires.
Schritt 4: Komprimierung auf Apache mit gzip und Brotli
Die Komprimierung von Textdateien übernimmt mod_deflate (gzip). Unter Debian und Ubuntu ist das Modul meist schon aktiv und über /etc/apache2/mods-enabled/deflate.conf für HTML, CSS, JavaScript und XML eingerichtet. Fehlt es, ergänzen Sie in der .htaccess:
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json image/svg+xml
</IfModule>
Komprimieren Sie keine JPEG-, PNG-, WebP- oder WOFF2-Dateien. Diese Formate sind bereits komprimiert, erneutes Packen kostet nur Rechenzeit.
Brotli packt Textdateien etwas kleiner als gzip. Mit eigenem Server aktivieren Sie das Modul mit a2enmod brotli und tragen die Regel in die Server-Konfiguration ein, etwa /etc/apache2/conf-available/brotli.conf mit anschließendem a2enconf brotli:
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS;DEFLATE text/html text/css text/javascript application/javascript application/json image/svg+xml
</IfModule>
Die Apache-Dokumentation zeigt nur BROTLI_COMPRESS allein. Die Kette BROTLI_COMPRESS;DEFLATE hat sich im Labor bewährt: Browser mit Brotli-Unterstützung erhalten Brotli, alle anderen gzip. Im Labor zeigte sich dabei ein Stolperstein. Solange die Debian-Datei deflate.conf dieselben Dateitypen schon für DEFLATE registriert, gewinnt gzip, auch wenn der Browser br anbietet. Erst nach Auskommentieren der Zeilen in deflate.conf lieferte der Server bei Accept-Encoding: gzip, deflate, br, zstd Brotli aus, Browser ohne Brotli erhielten weiterhin gzip. Die Einsparung ist messbar, aber klein: jQuery kam unkomprimiert auf 87553 Byte, mit gzip auf 30368 und mit Brotli auf 29769 Byte. Der große Schritt ist gzip, Brotli ist eine Ergänzung.
Laden Sie Apache nach Änderungen an der Server-Konfiguration neu, zuvor mit apachectl configtest prüfen, ob die Ausgabe Syntax OK lautet.
Verifizieren: curl -sI -H "Accept-Encoding: gzip" …/jquery.min.js zeigt Content-Encoding: gzip. Mit Brotli zeigt curl -sI -H "Accept-Encoding: gzip, deflate, br" … die Zeile Content-Encoding: br. Beide Antworten enthalten Vary: Accept-Encoding.
Schritt 5: Browser-Caching und gzip auf nginx
nginx kennt keine .htaccess. Die Regeln gehören in den server-Block Ihrer Website, unter Debian und Ubuntu meist unter /etc/nginx/sites-available/. In der Debian-Standarddatei /etc/nginx/nginx.conf steht bereits gzip on;, die Typen sind jedoch auskommentiert. Laut nginx-Dokumentation komprimiert gzip on ohne weitere Angabe nur text/html. Ergänzen Sie:
gzip on;
gzip_vary on;
gzip_types text/css text/javascript application/javascript application/json image/svg+xml;
location ~* \.(css|js|webp|avif|jpe?g|png|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public";
}
gzip_vary on ergänzt den Header Vary: Accept-Encoding, der standardmäßig fehlt. expires 1y setzt Expires und Cache-Control: max-age=31536000. Im Labor sendete nginx danach für jQuery:
HTTP/1.1 200 OK
Content-Type: application/javascript
Vary: Accept-Encoding
Expires: Thu, 30 Sep 2027 19:48:16 GMT
Cache-Control: max-age=31536000
Cache-Control: public
Content-Encoding: gzip
Zwei getrennte Cache-Control-Zeilen sind gültig, Browser werten sie als eine Liste. Wichtig ist die Vererbungsregel von add_header: Die nginx-Dokumentation sagt, dass Header einer höheren Ebene nur übernommen werden, wenn auf der aktuellen Ebene kein eigenes add_header steht. Haben Sie Sicherheitsheader im server-Block gesetzt, fehlen sie in diesem location-Block. Wiederholen Sie sie dort oder binden Sie sie per include ein. Hintergründe zu diesen Headern finden Sie in Sicherheitsheader für WordPress setzen.
Brotli ist in nginx nicht enthalten, sondern ein separates Modul. Ob es verfügbar ist, hängt von Ihrer Distribution ab, im Labor mit dem Debian-Paket haben wir es nicht eingesetzt. Testen Sie jede Änderung mit nginx -t und laden Sie erst bei „syntax is ok“ mit systemctl reload nginx neu.
Verifizieren: nginx -t meldet „test is successful“, und curl -sI -H "Accept-Encoding: gzip" auf eine CSS- oder JS-Datei zeigt Content-Encoding: gzip, Expires und Cache-Control. Prüfen Sie zusätzlich, ob Ihre Sicherheitsheader bei diesen Dateien noch erscheinen.
Schritt 6: Ergebnis im Browser kontrollieren und dauerhaft im Blick behalten
Öffnen Sie die Website in einem privaten Fenster, dann die Entwicklerwerkzeuge mit F12 und den Reiter „Netzwerk“. Laden Sie die Seite zweimal. Beim zweiten Aufruf zeigen CSS-, JS- und Bilddateien in der Spalte „Übertragen“ bzw. „Größe“ den Hinweis auf den Speicher- oder Datenträgercache statt einer Byte-Zahl. Die HTML-Seite selbst wird weiterhin vom Server geholt. Das ist gewollt.
Diese Kontrolle gehört nicht nur zur Einrichtung. Ein neues Theme, ein Wechsel auf nginx beim Hoster oder ein Plugin, das eigene Regeln in die .htaccess schreibt, verändert die Header ohne Rückmeldung. Wer solche Prüfungen nicht selbst im Kalender halten möchte, kann die laufende Pflege abgeben: Die WordPress-Wartung von wordpressupdate.de spielt Updates von WordPress, Plugins und Themes mit Kompatibilitätsprüfung ein, sichert die Website wöchentlich auf externen Speicher und richtet eine Firewall ein.
Verifizieren: Beim zweiten Laden kommen statische Dateien aus dem Browser-Cache, die HTML-Seite vom Server.
Typische Fehler
- Fehler 500 nach Änderung der .htaccess: Ein Modul fehlt und die Regel steht nicht in
<IfModule>. Das Apache-Fehlerlog nennt die Zeile, etwa „Invalid command 'Header'“. Spielen Sie die gesicherte.htaccessper SFTP zurück. - Regeln verschwinden nach dem Speichern der Permalinks: Sie stehen zwischen
# BEGIN WordPressund# END WordPress. Verschieben Sie sie oberhalb dieses Bereichs. - Brotli aktiviert, Server liefert trotzdem gzip: Eine andere Konfigurationsdatei registriert DEFLATE bereits für dieselben Typen. Im Labor war das die Debian-Datei
deflate.conf. - Keine Wirkung auf Shared Hosting mit nginx: Viele Hoster setzen nginx vor Apache oder betreiben reines nginx. Dann greifen
.htaccess-Regeln für statische Dateien nicht, die Einstellungen stehen im Kundenmenü oder sind Sache des Hosters. - Besucher sehen altes Design nach Theme-Anpassung: Eine Datei wird ohne Versionsparameter eingebunden. Binden Sie sie per
wp_enqueue_style()mit Version ein oder verkürzen Sie die Cache-Dauer. - Warenkorb oder Login-Zustand veraltet: Eine Regel betrifft auch
text/html, etwa überExpiresDefault. Nehmen Sie HTML aus dem Browser-Cache heraus.
Häufige Fragen
Brauche ich dafür ein Caching-Plugin?
Nein. Browser-Caching und Komprimierung sind Aufgaben des Webservers. Caching-Plugins schreiben oft ähnliche Regeln in die .htaccess, ihr Kern ist aber der Seiten-Cache auf dem Server. Nutzen Sie beides nur, wenn Sie wissen, welche Regeln das Plugin setzt, sonst stehen Anweisungen doppelt oder widersprüchlich in der Datei.
Wie hoch soll die Kompressionsstufe sein?
Die Standardwerte von Apache und nginx sind für die meisten Websites ausreichend. Höhere Stufen sparen wenige Byte und kosten bei jedem Aufruf Rechenzeit, bei dynamischen HTML-Seiten ohne Seiten-Cache spürbar.
Ist Komprimierung mit HTTPS ein Sicherheitsrisiko?
Die nginx-Dokumentation weist darauf hin, dass komprimierte Antworten über TLS für BREACH-Angriffe anfällig sein können. Das betrifft Seiten, die geheime Werte und vom Angreifer beeinflussbare Eingaben in derselben Antwort enthalten. Für CSS, JavaScript und Bilder ist das ohne Belang. Wer bei HTML vorsichtig sein will, komprimiert nur statische Dateien.
Wie leere ich den Browser-Cache meiner Besucher?
Gar nicht. Sie ändern stattdessen die Adresse der Datei, also den Versionsparameter oder den Dateinamen. Genau deshalb ist Schritt 2 wichtig.
Testumfang
Wir haben die Anleitung mit WordPress 7.1.2 unter Apache und nginx ausprobiert und die Header vor und nach der Änderung mit curl geprüft. Auffällig war, dass ein fehlendes Apache-Modul einen Fehler 500 auslöst.
Nicht geprüft haben wir LiteSpeed, Brotli unter nginx und die Kundenmenüs der Hoster. Nutzen Sie eine dieser Umgebungen, testen Sie die Änderung am besten zuerst auf einer Kopie.
Fazit
Mit einem Block in der .htaccess oder wenigen Zeilen in der nginx-Konfiguration laden wiederkehrende Besucher CSS, Skripte und Bilder aus dem eigenen Browser, und Textdateien schrumpfen auf rund ein Drittel. Voraussetzung ist, dass Dateien mit Versionsparameter eingebunden sind und HTML-Seiten nicht im Browser-Cache landen. Prüfen Sie die Header nach jedem Theme- oder Hosting-Wechsel erneut. Soll sich jemand anderes um Updates und Backups kümmern, übernimmt das die WordPress-Wartung.
Weiterführende Anleitungen und Quellen
- PHP-OPcache und PHP-Einstellungen für WordPress messen und anpassen
- Sicherheitsheader für WordPress: CSP, HSTS, X-Frame-Options
- WordPress auf HTTPS umstellen und Mixed Content beheben
- Apache-Dokumentation: mod_expires
- Apache-Dokumentation: mod_brotli
- nginx-Dokumentation: ngx_http_gzip_module
- nginx-Dokumentation: ngx_http_headers_module


