Security-Header erklärt: HSTS, CSP und die anderen
Welche Schutz-Kopfzeilen fehlen dir? Adresse eintragen, wir lesen aus, was dein Server mitschickt.
Geprüft wird die ganze Seite, nicht nur dieser Punkt. Wir rufen sie ab wie jeder Besucher — es wird nichts verändert.
Wie verbreitet fehlende Kopfzeilen tatsächlich sind, haben wir an 352 deutschen Websites gemessen: 75,3 % ohne Content-Security-Policy, 51,1 % ohne HSTS, 27,0 % ohne eine einzige der vier. Zur Studie 2026.
Security-Header sind kurze Anweisungen, die dein Server bei jeder Antwort mitschickt. Sie kosten nichts, verlangsamen nichts und verhindern trotzdem ganze Angriffsklassen. Der Aufwand liegt meist bei wenigen Zeilen in der Serverkonfiguration — mit zwei Ausnahmen, bei denen man vorsichtig sein muss. Dieser Text nennt für jeden Header den konkreten Wert, die Konfiguration für Apache, nginx und Plesk, und die Fallen, an denen es in der Praxis scheitert.
Betrifft dich das? Prüf deine Website kostenlos — dauert 30 Sekunden, keine Anmeldung nötig.
Die Reihenfolge, in der man sie einführt
Nicht alle Header sind gleich riskant. Wer bei null anfängt, geht am besten so vor:
| Schritt | Header | Risiko |
|---|---|---|
| 1 | X-Content-Type-Options, Referrer-Policy, X-Frame-Options | praktisch null — sofort setzen |
| 2 | Permissions-Policy | gering, nur prüfen ob Karten oder Videokonferenzen eingebunden sind |
| 3 | Strict-Transport-Security | mittel — vorher HTTPS auf allen Unterdomains prüfen |
| 4 | Content-Security-Policy | hoch — erst im Report-Only-Modus, das braucht Zeit |
Schritt 1 und 2 sind in zehn Minuten erledigt und decken bereits mehrere Angriffsklassen ab. Wer Schritt 4 aufschiebt, hat trotzdem schon viel gewonnen.
Die vier Kern-Header
| Header | Verhindert | Guter Wert |
|---|---|---|
Strict-Transport-Security | Herabstufung auf unverschlüsseltes HTTP | max-age=31536000; includeSubDomains |
Content-Security-Policy | Eingeschleuste Skripte (XSS) | siehe unten — abhängig von der Seite |
X-Frame-Options | Einbetten in fremde Seiten (Clickjacking) | SAMEORIGIN |
X-Content-Type-Options | Falsch geratene Dateitypen | nosniff |
Strict-Transport-Security (HSTS)
Tippt jemand deine-seite.de in die Adresszeile, versucht der Browser es zunächst
unverschlüsselt. Diese erste Anfrage lässt sich in einem fremden WLAN abfangen und umleiten —
die Weiterleitung auf HTTPS kommt zu spät. HSTS löst das: Der Browser merkt sich, dass diese Domain
nur über HTTPS erreichbar ist, und ruft sie ab dem zweiten Besuch gar nicht erst unverschlüsselt auf.
Entscheidend ist der Wert, nicht die bloße Anwesenheit des Headers. max-age=300 ist
faktisch wirkungslos — fünf Minuten Schutz. Empfohlen ist ein Jahr:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vorsicht: Mit includeSubDomains gilt die Vorgabe auch für alle
Unterdomains. Läuft dort etwas noch ohne HTTPS — ein alter Entwicklungsserver etwa —, ist es für
die Dauer von max-age nicht mehr erreichbar. Erst prüfen, dann setzen.
preload nur ergänzen, wenn du dir wirklich sicher bist: Die Eintragung in die
Browser-Vorlagelisten lässt sich nur mit Verzögerung rückgängig machen.
Der sichere Einstieg
Wer unsicher ist, fängt klein an und erhöht:
max-age=300ohneincludeSubDomains— eine Woche laufen lassen und beobachten, ob sich jemand meldet.max-age=86400(ein Tag), dann alle Unterdomains einzeln im Browser aufrufen und prüfen, dass jede per HTTPS erreichbar ist.- Erst dann
max-age=31536000; includeSubDomains.
Der Grund für die Vorsicht: Ein zu früh gesetztes includeSubDomains lässt sich nicht
zurückrufen. Der Wert steckt im Browser des Besuchers, nicht auf deinem Server. Man kann ihn nur
auf 0 setzen und warten, bis alle Besucher die Seite noch einmal aufrufen.
Content-Security-Policy (CSP)
Die CSP legt fest, aus welchen Quellen der Browser überhaupt etwas laden darf. Schafft es Angreifercode in deine Seite — etwa über ein Kommentarfeld —, verhindert eine gute CSP, dass er ausgeführt wird.
Der häufigste Fehler ist eine Policy, die zwar vorhanden, aber wirkungslos ist:
Content-Security-Policy: script-src 'self' 'unsafe-inline'
'unsafe-inline' erlaubt beliebigen Skriptcode direkt im HTML — also genau das, wovor die
CSP schützen soll. In Prüfberichten sieht so eine Seite gut aus, geschützt ist sie nicht.
Die wichtigsten Direktiven
| Direktive | Regelt | Empfehlung |
|---|---|---|
default-src | Auffangregel für alles, was nicht eigens genannt ist | 'self' |
script-src | JavaScript | 'self', bei Bedarf mit Nonce |
style-src | Stylesheets | 'self' |
img-src | Bilder | 'self' data: |
connect-src | fetch, XHR, WebSockets | 'self' |
object-src | Flash und andere Plugins | 'none' — es gibt keinen legitimen Grund mehr |
base-uri | das <base>-Tag | 'self' — sonst lassen sich alle relativen Pfade umbiegen |
frame-ancestors | wer die Seite einbetten darf | 'self' |
form-action | wohin Formulare abgeschickt werden dürfen | 'self' |
upgrade-insecure-requests | hebt http-Ressourcen automatisch auf https | setzen, kostet nichts |
object-src 'none' und base-uri 'self' werden fast immer vergessen, obwohl
beide keinerlei Nebenwirkungen haben. Sie gehören in jede Policy.
Ein brauchbarer Ausgangspunkt für eine einfache Seite:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self';
img-src 'self' data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self';
form-action 'self'; upgrade-insecure-requests
Inline-Skripte: der Nonce
Wer Inline-Skripte braucht, arbeitet mit einem Nonce: Der Server erzeugt pro Aufruf einen Zufallswert, schreibt ihn in den Header und in das erlaubte Skript-Tag. Fremder Code kennt den Wert nicht.
Content-Security-Policy: script-src 'self' 'nonce-r4nd0mW3rt'
<script nonce="r4nd0mW3rt"> ... </script>
Zwei Regeln dazu: Der Wert muss bei jedem Seitenaufruf neu erzeugt werden — ein fest eingetragener Nonce ist wertlos. Und er darf nicht vorhersagbar sein, also aus einem kryptografischen Zufallsgenerator stammen, nicht aus einem Zeitstempel.
Oft ist der einfachere Weg, die Inline-Skripte in eine eigene Datei auszulagern. Dann reicht
script-src 'self' ohne jeden Sonderfall. Achtung dabei: Ausgelagerter Code läuft
später als Inline-Code — wenn das Skript auf Elemente zugreift, die es vorher direkt im Fluss gab,
muss man die Ausführungsreihenfolge prüfen.
Kein Verstoß gegen die CSP sind übrigens <script type="application/ld+json">-Blöcke
mit strukturierten Daten. Sie enthalten keinen ausführbaren Code und werden von der Direktive nicht
erfasst — sie dürfen also stehen bleiben.
Die Einführung in drei Schritten
- Report-Only. Erst mit
Content-Security-Policy-Report-Onlyeinsetzen. Der Browser meldet Verstöße in der Entwicklerkonsole, blockiert aber nichts. - Beobachten. Ein paar Tage laufen lassen und alle Seitentypen durchklicken — Formulare, Shop, Buchung, Kartenansicht. Was in der Konsole auftaucht, entweder erlauben oder loswerden.
- Umschalten. Wenn nichts Relevantes mehr auftaucht, auf den echten Header umstellen. Danach noch einmal alle Seitentypen prüfen.
Wer Verstöße nicht von Hand in der Konsole sammeln will, kann sie sich melden lassen:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-bericht
Dann schickt der Browser jeden Verstoß als JSON an diese Adresse. Das lohnt sich vor allem bei Seiten mit vielen Unterseiten, wo man nicht alles selbst durchklicken kann.
Die kleineren Header
X-Content-Type-Options: nosniff— verbietet dem Browser, den Dateityp zu raten. Ohne diesen Header kann eine hochgeladene Bilddatei unter Umständen als Skript ausgeführt werden.Referrer-Policy: strict-origin-when-cross-origin— verhindert, dass vollständige URLs samt Parametern an fremde Seiten weitergegeben werden. Auch ein Datenschutzthema: Ohne diesen Header erfährt jede verlinkte Seite, von welcher genauen Unterseite jemand kam.Permissions-Policy: geolocation=(), camera=(), microphone=()— schaltet Browser-Funktionen ab, die deine Seite nicht braucht. Wichtig, wenn fremde Inhalte eingebettet sind: Ohne diesen Header darf ein eingebetteter Rahmen dieselben Rechte anfragen wie deine eigene Seite.Cross-Origin-Opener-Policy: same-origin— trennt deine Seite von Fenstern, die sie geöffnet hat. Schützt vor einer Klasse von Angriffen, bei denen ein geöffnetes Fenster auf die öffnende Seite zurückgreift.Cross-Origin-Resource-Policy: same-origin— verhindert, dass fremde Seiten deine Bilder und Skripte einbinden.
Wenn Anmeldung über ein Popup läuft — etwa bei PayPal oder einem
Single-Sign-on — darf Cross-Origin-Opener-Policy nicht auf same-origin
stehen, sonst bricht die Rückmeldung aus dem Popup ab. Richtig ist dann
same-origin-allow-popups. Das ist ein Fehler, der erst beim Bezahlen auffällt.
Was heute weg kann
X-XSS-Protection steht noch auf vielen Servern. Der Header schaltete einen
Browser-Filter ein, den es in modernen Browsern gar nicht mehr gibt — und der in alten Versionen
selbst Sicherheitslücken aufriss. Er kann ersatzlos entfernt werden; seine Aufgabe übernimmt die CSP.
Dasselbe gilt für Expect-CT, das seit 2021 wirkungslos ist.
Einrichten
Apache
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
</IfModule>
Das always ist wichtig: Ohne dieses Schlüsselwort fehlen die Header bei Fehlerseiten.
Gerade die 404-Seite ist aber die, die Angreifer am häufigsten zu sehen bekommen.
nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
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;
nginx-Falle: Sobald in einem location-Block ein einziges
add_header steht, gelten die Header aus dem übergeordneten Block dort nicht mehr.
Sie müssen dann im Unterblock wiederholt werden. Das ist einer der häufigsten Gründe dafür, dass
Header auf der Startseite erscheinen, auf Unterseiten aber fehlen.
Plesk
In Plesk führt der Weg über Websites & Domains → Apache- & nginx-Einstellungen. Dort gibt es zwei Felder: eines für zusätzliche Apache-Direktiven, eines für nginx. Läuft nginx als Reverse-Proxy vor Apache — die Standardkonfiguration —, gehören die Header in das nginx-Feld, sonst werden sie unter Umständen überschrieben. Nach dem Speichern lädt Plesk die Konfiguration selbst neu.
WordPress ohne Serverzugriff
Wenn du nicht an die Serverkonfiguration kommst, lassen sich die Header in der .htaccess
im Wurzelverzeichnis setzen — mit demselben Block wie oben. Vorher eine Sicherungskopie anlegen:
Ein Tippfehler in der .htaccess legt die ganze Seite lahm. Setze den Block
über den WordPress-Abschnitt, sonst wird er beim nächsten Speichern der
Permalink-Einstellungen überschrieben.
Wenn ein CDN davor hängt
Cloudflare und ähnliche Dienste können Header hinzufügen, ersetzen oder verschlucken. Zwei Konsequenzen: Erstens misst du mit einem Prüfdienst unter Umständen die Header des CDN, nicht deine eigenen. Zweitens kann ein im CDN gesetzter Header einen falsch konfigurierten Server verdecken — fällt das CDN aus oder wird umgangen, steht die Seite plötzlich ohne da. Setze die Header deshalb am Ursprungsserver und benutze das CDN höchstens ergänzend.
Danach prüfen
Header werden erstaunlich oft gesetzt und wirken trotzdem nicht — wegen Zwischenspeichern, wegen eines vorgeschalteten Proxys oder wegen der nginx-Falle. Am schnellsten geht die Kontrolle auf der Kommandozeile:
curl -sI https://deine-seite.de | grep -i -E "strict-transport|content-security|x-frame|x-content|referrer|permissions"
Das Wichtige daran: mehrere Unterseiten prüfen, nicht nur die Startseite — und auch eine Adresse, die es nicht gibt, damit die Fehlerseite mit dabei ist.
Apexly bewertet dabei nicht nur, ob ein Header da ist, sondern auch, ob sein Wert
tatsächlich schützt — ein max-age=300 oder eine CSP mit 'unsafe-inline'
zählt bei uns nicht als erfüllt.
Die häufigsten Fehler
| Fehler | Folge |
|---|---|
CSP mit 'unsafe-inline' | Header vorhanden, Schutz nicht |
HSTS mit sehr kurzem max-age | faktisch wirkungslos |
always in Apache vergessen | Fehlerseiten ohne Header |
nginx: add_header im Unterblock | Header verschwinden auf Unterseiten |
COOP auf same-origin trotz Bezahl-Popup | Zahlung bricht ab |
X-XSS-Protection noch gesetzt | wirkungslos, in alten Browsern sogar schädlich |
| Nur die Startseite geprüft | falsches Sicherheitsgefühl |
Häufige Fragen
Welcher Security-Header ist am wichtigsten?
Wenn du nur einen setzen kannst: Strict-Transport-Security. Er ist in einer Zeile erledigt, kann kaum etwas kaputtmachen und verhindert eine ganze Angriffsklasse. Die Content-Security-Policy bringt am meisten, ist aber auch am aufwendigsten.
Kann ein Security-Header meine Website kaputtmachen?
Eine zu strenge Content-Security-Policy kann Skripte blockieren und damit sichtbar Funktionen lahmlegen. Deshalb erst im Report-Only-Modus testen. HSTS ist gefährlich, wenn HTTPS nicht überall funktioniert — Browser merken sich die Vorgabe für die angegebene Dauer. Die übrigen Header sind praktisch risikofrei.
Reicht es, die Header nur auf der Startseite zu setzen?
Nein. Header wirken pro Antwort. Sie gehören in die Serverkonfiguration, damit jede Seite und jede Datei sie mitliefert — einschließlich der Fehlerseiten.
Was ist der Unterschied zwischen X-Frame-Options und frame-ancestors?
Beide verhindern, dass deine Seite in einen fremden Rahmen eingebettet wird. frame-ancestors ist die modernere Variante innerhalb der CSP und kann mehrere erlaubte Quellen nennen; X-Frame-Options ist der ältere, gröbere Header. Setze beide: den alten für alte Browser, den neuen für alles andere.
Wie mache ich HSTS wieder rückgängig?
Indem du max-age=0 auslieferst und wartest. Der Browser vergisst die Vorgabe erst, wenn er die Seite noch einmal aufruft und den neuen Wert sieht. Bei preload geht es nur über eine Abmeldung bei der Vorlageliste, und die dauert Monate — deshalb die Warnung oben.
Zählt ein JSON-LD-Block als Inline-Skript?
Nein. <script type="application/ld+json"> enthält Daten, keinen ausführbaren Code, und wird von script-src nicht erfasst. Strukturierte Daten für Suchmaschinen können also bleiben, auch bei strenger CSP.
Muss ich die Header im CDN oder auf dem Server setzen?
Auf dem Server. Wird nur im CDN gesetzt, fehlt der Schutz überall dort, wo das CDN umgangen wird — und ein Ausfall des Dienstes nimmt ihn gleich mit. Das CDN darf ergänzen, nicht ersetzen.
In welchem Rahmen solche Maßnahmen zur Pflicht werden, steht im NIS2-Ratgeber.
Prüf deine Website in 30 Sekunden
Ein Scan pro Tag ist kostenlos — ohne Anmeldung, ohne Installation.
Stand: 6. August 2026. Dieser Text erklärt technische Zusammenhänge und ist keine Rechtsberatung.