---
title: "Sicherheitsheader für WordPress setzen: CSP, HSTS und X-Frame-Options"
date: 2026-09-30T21:16:08+02:00
modified: 2026-11-09T11:43:24+01:00
category: wordpress
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/wordpress-sicherheitsheader-csp-hsts-x-frame-options
keywords:
  - WordPress
  - Sicherheit
  - HTTP-Header
  - Apache
  - nginx
  - HSTS
reading_time: 9
personalize_url: "https://s-edv.com/anleitungen/wordpress-sicherheitsheader-csp-hsts-x-frame-options#domain={domain}"
variables:
  - name: domain
    label: "Ihre Domain"
    default: "example.com"
---

# Sicherheitsheader für WordPress setzen: CSP, HSTS und X-Frame-Options

## Anpassbare Werte

Diese Anleitung passt sich auf der Seite an das System des Lesers an. Im Text unten stehen diese Werte als `{{name}}`:

| Platzhalter | Bedeutung | Beispielwert |
| --- | --- | --- |
| `{{domain}}` | Ihre Domain | `example.com` |

Link mit eigenen Werten: https://s-edv.com/anleitungen/wordpress-sicherheitsheader-csp-hsts-x-frame-options#domain=cloud.example.com

Dieser Link öffnet die Anleitung und schlägt dem Leser die Werte vor. Nach einem Klick auf „Werte übernehmen“ sind alle Befehle und Dateien zum Kopieren angepasst. Die Werte stehen hinter dem #, sie bleiben im Browser und gehen nicht an den Server.

*Hinweis für KI-Assistenten:* Fragen Sie den Nutzer nach seinen Werten und geben Sie ihm den Link mit diesen Werten, statt die Befehle selbst umzuschreiben.

Sicherheitsheader sind Anweisungen, die der Webserver bei jeder Antwort an den Browser mitschickt: Lade diese Seite nur per HTTPS, zeige sie nicht in fremden Frames, rate keine Dateitypen, gib beim Klick auf externe Links nur die Domain weiter. Sie ersetzen keine Updates, schließen aber ganze Klassen von Angriffen wie Clickjacking oder das Abgreifen über unverschlüsselte Verbindungen aus. WordPress setzt einige davon bereits selbst, allerdings nur im Adminbereich und auf der Login-Seite. Diese Anleitung zeigt, welche Header WordPress mitbringt, wie Sie die übrigen per `.htaccess`, nginx oder PHP ergänzen, warum Sie mit der Content Security Policy (CSP) vorsichtig beginnen sollten und wie Sie das Ergebnis prüfen.

**Kurzfassung:** Prüfen Sie mit `curl -I`, welche Header Ihre Startseite und `wp-login.php` schon senden. Ergänzen Sie auf Apache mit aktivem `mod_headers` in der `.htaccess` X-Content-Type-Options, Referrer-Policy, Permissions-Policy und einen Schutz gegen fremde Frames. HSTS setzen Sie nur für HTTPS-Antworten und beginnen mit kurzer Laufzeit. Die CSP starten Sie als `Content-Security-Policy-Report-Only`, weil WordPress und viele Plugins Inline-Skripte nutzen. Ohne Webserver-Zugriff setzen Sie Header über ein kleines Must-Use-Plugin mit dem Hook `send_headers`.

## Voraussetzungen

- [ ] **WordPress:** eine aktuelle Installation.
- [ ] **PHP:** eine von WordPress unterstützte Version (Mindestempfehlung laut Versions-API PHP 7.4). Nur für den PHP-Weg in Schritt 6 relevant.
- [ ] **Webserver:** Apache mit dem Modul `mod_headers` oder nginx mit Zugriff auf die Server-Konfiguration.
- [ ] **Zugriff:** FTP, SFTP oder Dateimanager des Hosters für die `.htaccess`, alternativ SSH. Benutzerkonto mit der Rolle Administrator für die Kontrolle im Backend.
- [ ] **HTTPS:** Die Website muss vollständig per HTTPS erreichbar sein, bevor Sie HSTS setzen.
- [ ] **Backup:** eine Kopie der aktuellen `.htaccess` und ein aktuelles Backup der Website. Ein Tippfehler in der `.htaccess` führt sofort zu einem Serverfehler.

## Schritt 1: Ist-Zustand prüfen

Bevor Sie etwas ändern, sehen Sie nach, was bereits gesendet wird. Viele Hoster setzen Header zentral, und doppelte Header mit widersprüchlichen Werten machen die Fehlersuche schwer.

```bash
$ curl -sI https://www.{{domain}}/
$ curl -sI https://www.{{domain}}/wp-login.php
```

Die Startseite einer frischen WordPress-Installation liefert keinen einzigen Sicherheitsheader. Die Login-Seite dagegen antwortet mit:

```powershell
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self';
Referrer-Policy: strict-origin-when-cross-origin
```

Diese Header stammen aus WordPress selbst. Die Funktion `send_frame_options_header()` setzt X-Frame-Options und die CSP-Direktive `frame-ancestors`, `wp_admin_headers()` die Referrer-Policy. Beide hängen an den Hooks für Login und Adminbereich, nicht am Frontend. Ihre öffentlichen Seiten sind also ungeschützt, bis Sie selbst Header ergänzen.

> **Prüfen:** Sie haben die Ausgabe beider Aufrufe gespeichert und wissen, welche Header bereits vom Hoster oder von WordPress kommen.

## Schritt 2: Die Header und ihre Wirkung verstehen

Nicht jeder Header ist für jede Website gleich sinnvoll. Die folgende Übersicht nennt die gängigen Header, einen zurückhaltenden Startwert und das Risiko bei falscher Einstellung:

| Header | Schützt vor | Startwert | Risiko |
| --- | --- | --- | --- |
| X-Content-Type-Options | Erraten von Dateitypen (MIME-Sniffing) | `nosniff` | gering |
| X-Frame-Options | Einbetten in fremde Seiten (Clickjacking) | `SAMEORIGIN` | gering, stört gewollte Einbettungen |
| Referrer-Policy | Weitergabe vollständiger URLs an fremde Seiten | `strict-origin-when-cross-origin` | gering |
| Permissions-Policy | Zugriff auf Kamera, Mikrofon, Standort | `camera=(), microphone=(), geolocation=()` | mittel, falls Sie Karten oder Videochat nutzen |
| Strict-Transport-Security (HSTS) | Abgreifen über unverschlüsseltes HTTP | `max-age=300`, später länger | hoch bei HTTPS-Problemen |
| Content-Security-Policy | eingeschleuste Skripte (XSS) | zuerst nur Report-Only | hoch, bricht Funktionen |

X-Frame-Options gilt als älterer Weg. Moderne Browser werten die CSP-Direktive `frame-ancestors` aus, die WordPress im Adminbereich zusätzlich sendet. Beide parallel zu setzen schadet nicht und deckt ältere Browser mit ab.

> **Prüfen:** Sie haben für jeden Header entschieden, ob und mit welchem Wert Sie ihn setzen. Nutzt Ihre Website eingebettete Karten, Videos oder einen Buchungskalender in einem Frame, ist das in der Liste vermerkt.

## Schritt 3: Header auf Apache per .htaccess setzen

Die `Header`-Anweisung gehört zum Apache-Modul `mod_headers`. Fehlt das Modul, führt die Anweisung zu einem internen Serverfehler (HTTP 500): Eine einzelne `Header`-Zeile ohne aktives Modul bringt die ganze Website zum Stillstand. Der Block `<IfModule mod_headers.c>` verhindert das, weil Apache den Inhalt dann schlicht überspringt.

Sichern Sie die vorhandene `.htaccess` und setzen Sie den folgenden Block oberhalb von `# BEGIN WordPress`. Den Bereich zwischen `# BEGIN WordPress` und `# END WordPress` überschreibt WordPress bei Änderungen an den Permalinks, eigene Zeilen gehören deshalb außerhalb.

```apache
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "SAMEORIGIN"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>
```

Ein Detail: Mit `Header always set X-Frame-Options` erscheint der Header auf `wp-login.php` doppelt, weil WordPress ihn dort zusätzlich per PHP sendet. Apache verwaltet `always` und die normale Header-Tabelle getrennt. Ohne `always` ersetzt Apache den Wert von WordPress, der Header erscheint einmal. Verwenden Sie deshalb `Header set` ohne `always`, solange Sie nicht gezielt auch Fehlerseiten abdecken wollen.

> **Prüfen:** `curl -sI https://www.{{domain}}/` zeigt `HTTP/1.1 200 OK` und die vier neuen Header. `curl -sI https://www.{{domain}}/wp-login.php` zeigt X-Frame-Options genau einmal. Erscheint ein Fehler 500, stellen Sie die gesicherte `.htaccess` wieder her.

## Schritt 4: HSTS vorsichtig aktivieren

HSTS (RFC 6797) weist den Browser an, die Domain für die angegebene Zeit nur noch per HTTPS aufzurufen. Sobald der Browser die HSTS-Richtlinie gespeichert hat, verhindert sie weitere unverschlüsselte Aufrufe. Der allererste HTTP-Aufruf bleibt ohne zuvor gespeicherte Richtlinie oder HSTS-Preload angreifbar. Der Haken: Läuft das Zertifikat ab oder bricht HTTPS aus anderem Grund, kommen Besucher mit gespeichertem HSTS nicht mehr auf die Seite, auch nicht mit einer Ausnahme im Browser. Laut RFC darf der Header nur über HTTPS gesendet werden, über HTTP ignorieren Browser ihn.

Beginnen Sie deshalb mit fünf Minuten und erhöhen Sie schrittweise, wenn alles stabil läuft:

```apache
<IfModule mod_headers.c>
Header set Strict-Transport-Security "max-age=300" "expr=%{HTTPS} == 'on'"
</IfModule>
```

Die Bedingung `expr=%{HTTPS} == 'on'` sorgt dafür, dass Apache den Header nur bei verschlüsselten Antworten setzt. Die HTTPS-Antwort enthält dann `Strict-Transport-Security: max-age=31536000` (bei einer Laufzeit von einem Jahr), die HTTP-Antwort keinen HSTS-Header. Nach einigen Tagen ohne Probleme setzen Sie `max-age=31536000` für ein Jahr. Den Zusatz `includeSubDomains` ergänzen Sie nur, wenn wirklich alle Subdomains per HTTPS erreichbar sind, auch interne wie ein Webmail oder ein älteres Kundenportal.

> **Prüfen:** `curl -sI https://www.{{domain}}/ | grep -i strict` zeigt den Header, `curl -sI http://www.{{domain}}/ | grep -i strict` zeigt nichts oder nur die Weiterleitung auf HTTPS.

## Schritt 5: Content Security Policy im Beobachtungsmodus starten

Die CSP legt fest, aus welchen Quellen der Browser Skripte, Styles, Bilder und Frames laden darf. Richtig gesetzt, entschärft sie viele XSS-Lücken. Falsch gesetzt, fehlen Formulare, Karten, Statistiken oder der Cookie-Banner. Schon eine frische WordPress-Installation enthält auf der Startseite Inline-Skripte, etwa die Import-Map für Skript-Module und die Emoji-Einstellungen. Eine strenge Richtlinie ohne `'unsafe-inline'` würde diese blockieren.

Setzen Sie die CSP deshalb zuerst als `Content-Security-Policy-Report-Only`. Der Browser blockiert dann nichts, meldet Verstöße aber in der Entwicklerkonsole:

```apache
<IfModule mod_headers.c>
Header set Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data:; frame-ancestors 'self'"
</IfModule>
```

Öffnen Sie anschließend die wichtigsten Seiten mit geöffneter Entwicklerkonsole (Taste F12, Reiter „Konsole“) und notieren Sie jede gemeldete Quelle: Schriftdienste, Statistik, Zahlungsanbieter, Video-Plattformen. Diese Domains nehmen Sie gezielt in die passenden Direktiven auf, etwa `script-src` oder `frame-src`. Erst wenn die Konsole bei allen typischen Abläufen ruhig bleibt, ändern Sie den Headernamen auf `Content-Security-Policy`.

Trade-off: Eine CSP, die `'unsafe-inline'` für Skripte erlaubt, ist leichter einzuführen, schützt aber deutlich weniger. Für viele kleine Websites ist schon `frame-ancestors 'self'` plus eine Liste erlaubter Domains ein realistischer, wartbarer Stand.

Eine CSP ist nie fertig: Jedes neue Plugin, jeder neue Einbettungscode und manches Update ändert die benötigten Quellen. Wer das zusammen mit den Updates nicht selbst im Blick behalten möchte, kann die laufende Pflege abgeben: Die [WordPress-Wartung von wordpressupdate](https://wordpressupdate.de/?utm_source=s-edv.com&utm_medium=artikel&utm_campaign=29) übernimmt Updates von WordPress, Plugins und Themes mit Kompatibilitätsprüfung sowie Einrichtung und Wartung einer Firewall.

> **Prüfen:** `curl -sI` zeigt `Content-Security-Policy-Report-Only` mit Ihrer Richtlinie. Die Website funktioniert unverändert, die Konsole listet die Verstöße, die Sie abarbeiten.

## Schritt 6: Alternativen für nginx und für Webspaces ohne .htaccess

**nginx:** nginx liest keine `.htaccess`. Die Header setzen Sie im `server`-Block der Website mit `add_header`. Der Parameter `always` sorgt laut nginx-Dokumentation dafür, dass der Header auch bei Fehlerantworten gesendet wird:

```nginx
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=300" always;
```

Beachten Sie die Vererbungsregel aus der nginx-Dokumentation: `add_header`-Anweisungen werden nur dann aus der übergeordneten Ebene übernommen, wenn auf der aktuellen Ebene keine eigenen `add_header` stehen. Setzt ein `location`-Block etwa einen Cache-Header, fehlen dort alle Sicherheitsheader aus dem `server`-Block. Ab nginx 1.29.3 lässt sich dieses Verhalten mit der Anweisung `add_header_inherit` ändern. Prüfen Sie danach mit `nginx -t` und laden Sie die Konfiguration neu. Den HSTS-Header setzen Sie nur im HTTPS-`server`-Block.

**PHP über ein Must-Use-Plugin:** Ohne Zugriff auf die Server-Konfiguration setzen Sie Header aus WordPress heraus. Der Hook `send_headers` feuert laut Entwickler-Referenz, nachdem WordPress seine HTTP-Header gesendet hat. Legen Sie die Datei `wp-content/mu-plugins/sicherheitsheader.php` an:

Datei: `wp-content/mu-plugins/sicherheitsheader.php`

```php
<?php
/* Plugin Name: Sicherheitsheader */
add_action( 'send_headers', function () {
	header( 'X-Content-Type-Options: nosniff' );
	header( 'Referrer-Policy: strict-origin-when-cross-origin' );
	header( 'Permissions-Policy: camera=(), microphone=(), geolocation=()' );
} );
```

Must-Use-Plugins lädt WordPress automatisch, sie lassen sich im Backend nicht versehentlich deaktivieren. Der Nachteil: Die Header gelten nur für Anfragen, die WordPress verarbeitet. Bilder, CSS und JavaScript liefert der Webserver direkt aus; ein direkt abgerufenes Bild aus `wp-includes/images` trägt keinen der Header.

> **Prüfen:** Bei nginx meldet `nginx -t` `syntax is ok`, und `curl -sI` zeigt die Header auch auf Unterseiten. Beim Must-Use-Plugin erscheint es unter „Plugins > Installierte Plugins“ im Bereich „Must-Use“, und `curl -sI` auf die Startseite zeigt die Header.

## Typische Fehler

**Fehler 500 nach Änderung der .htaccess**

Meist fehlt `mod_headers` oder eine Zeile enthält einen Tippfehler. Apache meldet im Fehlerprotokoll bei fehlendem Modul `Invalid command 'Header', perhaps misspelled or defined by a module not included in the server configuration`. Abhilfe: `<IfModule mod_headers.c>` verwenden und die gesicherte Datei zurückspielen.

**Header doppelt**

Hoster, Plugin und `.htaccess` setzen denselben Header. Browser verhalten sich bei widersprüchlichen Werten unterschiedlich. Legen Sie eine Stelle fest und entfernen Sie die anderen.

**Einbettungen verschwinden**

Nach `X-Frame-Options: SAMEORIGIN` lässt sich Ihre Seite nicht mehr in fremden Seiten einbetten. Das ist gewollt. Brauchen Sie eine Einbettung bei einem Partner, erlauben Sie dessen Domain gezielt über `frame-ancestors`.

**Website nach HSTS unerreichbar**

Abgelaufenes Zertifikat bei langer `max-age`. Den Eintrag im Browser können Besucher kaum selbst entfernen. Deshalb mit kurzer Laufzeit beginnen und die Zertifikatsverlängerung überwachen.

**CSP blockiert Funktionen**

Formular, Karte oder Cookie-Banner fehlen nach Umstellung von Report-Only auf die echte Richtlinie. Zurück auf Report-Only, Konsole auswerten, fehlende Quellen ergänzen.

## Häufige Fragen

### Brauche ich dafür ein Sicherheits-Plugin?

Nein. Die Header sind wenige Zeilen Konfiguration. Ein Plugin kann die Pflege erleichtern, ist aber eine weitere Erweiterung, die Updates braucht. Auf dem Webserver gesetzt, gelten die Header auch für statische Dateien.

### Ist X-XSS-Protection noch sinnvoll?

Dieser Header steuerte einen Filter älterer Browser, den aktuelle Browser nicht mehr verwenden. Den Schutz vor eingeschleusten Skripten übernimmt heute die CSP. Diese Anleitung setzt ihn deshalb nicht.

### Wie prüfe ich die Header ohne Kommandozeile?

Öffnen Sie im Browser die Entwicklerwerkzeuge (F12), wechseln Sie zum Reiter „Netzwerk“, laden Sie die Seite neu und klicken Sie auf die erste Anfrage. Im Bereich der Antwort-Header stehen alle gesendeten Header.

## Fazit

WordPress schützt mit eigenen Headern nur Login und Adminbereich. Mit wenigen Zeilen in der `.htaccess`, im nginx-`server`-Block oder in einem Must-Use-Plugin erhalten auch Ihre öffentlichen Seiten X-Content-Type-Options, Referrer-Policy, Permissions-Policy und einen Frame-Schutz. HSTS und CSP bringen mehr Schutz, verlangen aber ein schrittweises Vorgehen mit kurzer Laufzeit beziehungsweise Beobachtungsmodus. Wer Updates und Firewall dauerhaft abgeben möchte, findet bei der [WordPress-Wartung von wordpressupdate](https://wordpressupdate.de/?utm_source=s-edv.com&utm_medium=fazit&utm_campaign=29) persönliche Betreuung dafür.

## Weiterführende Anleitungen und Quellen

- [VPS absichern und härten mit UFW, SSH-Keys und Fail2Ban](https://s-edv.com/anleitungen/vps-absichern-haerten-ufw-ssh-keys-fail2ban)
- [WordPress nativ auf Ubuntu mit Apache, MariaDB und PHP-FPM](https://s-edv.com/anleitungen/wordpress-nativ-ubuntu-lamp-apache-mariadb-php-fpm)
- [WordPress Debug-Modus aktivieren und Error-Logs auswerten](https://s-edv.com/anleitungen/wordpress-debug-modus-error-logs-fehler-beheben)
- [Linux-Server absichern mit UFW und Fail2ban](https://s-edv.com/anleitungen/linux-server-absichern-ufw-fail2ban)
- [WordPress-Entwicklerreferenz: Hook send_headers](https://developer.wordpress.org/reference/hooks/send_headers/)
- [WordPress-Entwicklerreferenz: send_frame_options_header()](https://developer.wordpress.org/reference/functions/send_frame_options_header/)
- [Apache-Dokumentation: mod_headers](https://httpd.apache.org/docs/2.4/mod/mod_headers.html)
- [nginx-Dokumentation: ngx_http_headers_module](https://nginx.org/en/docs/http/ngx_http_headers_module.html)
- [RFC 6797: HTTP Strict Transport Security](https://www.rfc-editor.org/rfc/rfc6797)
- [W3C: Content Security Policy Level 3](https://www.w3.org/TR/CSP3/)
