---
title: "Browser-Caching und Komprimierung für WordPress auf Apache und nginx einrichten"
date: 2026-09-30T21:53:33+02:00
modified: 2026-11-10T17:20:59+01:00
category: wordpress
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/wordpress-browser-caching-komprimierung-gzip-brotli-apache-nginx
keywords:
  - WordPress
  - Performance
  - Apache
  - nginx
  - Caching
reading_time: 10
---

# Browser-Caching und Komprimierung für WordPress auf Apache und nginx einrichten

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 mit WordPress 7.1.2, Apache 2.4 und nginx 1.26 geprüft, einschließlich der Fehlerfälle.

**Kurzfassung:** Prüfen Sie zuerst mit `curl -I`, welche Header Ihr Server heute sendet. Geben Sie statischen Dateien wie CSS, JavaScript, Bildern und Schriften eine lange Gültigkeit über `Cache-Control` und `Expires`, HTML-Seiten dagegen nicht. Das ist sicher, weil WordPress seine Dateien mit einem Versionsparameter wie `?ver=7.1.2` einbindet. Aktivieren Sie gzip, auf Apache zusätzlich Brotli, und prüfen Sie das Ergebnis mit `Accept-Encoding`. Auf Apache helfen `<IfModule>`-Blöcke gegen Serverfehler 500, wenn ein Modul fehlt.

## Voraussetzungen

- [ ] **WordPress:** eine aktuelle Installation, getestet mit WordPress 7.1.2 und PHP 8.4. Im Test läuft das Update auf WordPress 7.1.3 ohne Fehler und ohne Datenbank-Update. Core-Dateien tragen danach `?ver=7.1.3`.
- [ ] **Webserver:** Apache 2.4 mit `mod_expires`, `mod_headers` und `mod_deflate`, optional `mod_brotli`. Oder nginx, getestet mit 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:** `curl` auf Ihrem Rechner (unter Windows 10 und 11 bereits enthalten) oder die Entwicklerwerkzeuge des Browsers, Reiter „Netzwerk“.
- [ ] **Backup:** eine Kopie der aktuellen `.htaccess` bzw. der nginx-Konfiguration und ein aktuelles Backup der Website. Ein Tippfehler in der `.htaccess` legt 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:

```bash
$ 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 Test lieferte ein unveränderter Apache aus dem offiziellen WordPress-Image diese Antwort:

```plaintext
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.

> **Prüfen:** 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 Beispiel 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.

> **Prüfen:** 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.

```apache
# 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 Test führte eine `Header`-Zeile ohne Klammer bei abgeschaltetem `mod_headers` sofort zu Fehler 500 mit dem Eintrag:

```plaintext
/var/www/html/.htaccess: Invalid command 'Header', perhaps misspelled or defined by a module not included in the server configuration
```

> **Prüfen:** `curl -sI` auf die jQuery-Datei zeigt jetzt `Cache-Control: public, max-age=31536000` und ein `Expires`-Datum ein Jahr in der Zukunft. Im Test 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`:

```apache
<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`:

```apache
<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 Test bewährt: Browser mit Brotli-Unterstützung erhalten Brotli, alle anderen gzip. Im Test 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.

> **Prüfen:** `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:

```nginx
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 Test sendete nginx danach für jQuery:

```plaintext
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](https://s-edv.com/anleitungen/wordpress-sicherheitsheader-csp-hsts-x-frame-options).

Brotli ist in nginx nicht enthalten, sondern ein separates Modul. Ob es verfügbar ist, hängt von Ihrer Distribution ab. Testen Sie jede Änderung mit `nginx -t` und laden Sie erst bei „syntax is ok“ mit `systemctl reload nginx` neu.

> **Prüfen:** `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.

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](https://wordpressupdate.de/?utm_source=s-edv.com&utm_medium=artikel&utm_campaign=58) 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.

> **Prüfen:** 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 `.htaccess` per SFTP zurück.

**Regeln verschwinden nach dem Speichern der Permalinks**

Sie stehen zwischen `# BEGIN WordPress` und `# 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 Beispiel 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 über `ExpiresDefault`. 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.

## 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](https://wordpressupdate.de/?utm_source=s-edv.com&utm_medium=fazit&utm_campaign=58).

## Weiterführende Anleitungen und Quellen

- [PHP-OPcache und PHP-Einstellungen für WordPress messen und anpassen](https://s-edv.com/anleitungen/wordpress-php-opcache-einstellungen-memory-limit-upload-optimieren)
- [Sicherheitsheader für WordPress: CSP, HSTS, X-Frame-Options](https://s-edv.com/anleitungen/wordpress-sicherheitsheader-csp-hsts-x-frame-options)
- [WordPress auf HTTPS umstellen und Mixed Content beheben](https://s-edv.com/anleitungen/wordpress-https-umstellen-ssl-mixed-content-beheben)
- [Apache-Dokumentation: mod_expires](https://httpd.apache.org/docs/2.4/mod/mod_expires.html)
- [Apache-Dokumentation: mod_brotli](https://httpd.apache.org/docs/2.4/mod/mod_brotli.html)
- [nginx-Dokumentation: ngx_http_gzip_module](https://nginx.org/en/docs/http/ngx_http_gzip_module.html)
- [nginx-Dokumentation: ngx_http_headers_module](https://nginx.org/en/docs/http/ngx_http_headers_module.html)
