Content Security Policy in Nginx
Eine CSP in Nginx zu deployen ist üblicherweise ein Einzeiler in Ihrem Server-Block. Diese Anleitung deckt die richtige Platzierung ab, das always-Flag das in vielen Tutorials unter den Tisch fällt, und die Trückel rund um Header-Vererbung in verschachtelten Locations.
Das minimale Snippet
Öffnen Sie die Konfigurationsdatei Ihrer Site (auf Debian/Ubuntu meist /etc/nginx/sites-enabled/ihre-seite, auf RHEL/CentOS oft /etc/nginx/conf.d/ihre-seite.conf) und fügen Sie Folgendes in den server { }-Block ein:
server {
listen 443 ssl http2;
server_name example.com;
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'" always;
# ... Rest der Config ...
}
Danach validieren und neu laden:
sudo nginx -t
sudo systemctl reload nginx
Reload (nicht Restart) reicht für Header-Änderungen völlig aus und schmeißt keine bestehenden Verbindungen weg.
Warum das always-Flag entscheidend ist
Das ist das am häufigsten vergessene Detail in Nginx-CSP-Tutorials — und ein echtes Sicherheitsproblem.
Ohne always wendet Nginx add_header-Direktiven nur auf 200-, 201-, 204-, 206-, 301-, 302-, 303-, 304-, 307- und 308-Responses an. Heisst Ihre 404-Not-Found-, 403-Forbidden- und 500-Internal-Server-Error-Seiten haben gar keinen CSP-Header. Solche Fehlerseiten spiegeln oft benutzerkontrollierten Inhalt zurück (denken Sie an eine falsch eingegebene URL die in die Seite reingedruckt wird) — genau dort lauern XSS-Bugs.
Das always-Flag zwingt Nginx, den Header auf jeden Response anzuwenden, egal welcher Status-Code. Bei Security-Headern ist es Pflicht.
Report-Only-Rollout
Deployen Sie den Header zuerst als Content-Security-Policy-Report-Only. Der Browser protokolliert Verstöße in der Konsole, blockiert aber nichts. So können Sie 1–2 Wochen beobachten und die Warnungen aufräumen, bevor Sie zum Durchsetzen wechseln. Seite öffnen, F12 drücken, und nach Zeilen wie dieser suchen:
[Report Only] Refused to load the script 'https://cdn.example.com/widget.js' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline'".
Jede Zeile ist eine echte Ressource die Ihre Seite lädt und die die Policy blockieren würde. Entweder den Origin freigeben oder die Abhängigkeit loswerden. Sobald die Konsole ruhig ist, den Header-Namen von Content-Security-Policy-Report-Only zu Content-Security-Policy ändern und Nginx reloaden.
Typische Fehlerquellen
Verschachtelte add_header-Direktiven löschen sich gegenseitig
Diese Falle erwischt jeden mindestens einmal. Wenn Sie add_header sowohl im server-Block UND in einem verschachtelten location-Block haben, ersetzen die Header im location-Block die aus dem Parent-Context komplett — sie werden nicht gemerged. Ein einzelner Header in einem location löscht also still und heimlich alle Header aus dem Parent-server.
server {
add_header X-Frame-Options DENY always;
add_header Content-Security-Policy "default-src 'self'" always;
location /api/ {
add_header X-API-Version 1 always;
# FALSCH: X-Frame-Options und CSP werden hier NICHT angewendet.
# Sie müssen sie hier wiederholen.
}
}
Lösung: Alle Security-Header in jedem location-Block wiederholen der auch nur einen einzigen Header hinzufügt. Oder alternativ eine map-Variable + eine einzige add_header-Direktive im Server-Level, um die Duplizierung zu vermeiden.
Der Header taucht gar nicht auf
Mit curl -I https://ihre-seite.example verifizieren. Wenn die CSP im Output fehlt, prüfen Sie:
- Läuft
nginx -tsauber durch? Syntaxfehler werden stillschweigend übersprungen. - Trifft Ihr Request den richtigen Server-Block? Prüfen Sie das
server_name-Matching. - Fängt ein
location-Block den Request zuerst ab und überschreibt damit die Header? (Siehe oben.) - Läuft ein Proxy oder CDN vor Nginx das die Header abschneidet? Cloudflare zum Beispiel entfernt CSP im Normalfall nicht, aber falsch konfigurierte Reverse-Proxies tun das manchmal.
Mehrere CSP-Header gleichzeitig
Wenn Sie mehrere add_header Content-Security-Policy ...-Direktiven haben (typischerweise aus Include-Dateien), sendet Nginx alle. Browser behandeln mehrere CSP-Header als Schnittmenge, was restriktiver ist als jede einzelne Policy. Resultat: Sachen werden blockiert die Sie nicht erwartet haben. Zu einem einzigen Header konsolidieren.
HTTP/2 und Groß-/Kleinschreibung
HTTP/2 verlangt technisch gesehen Header-Namen in Kleinbuchstaben. Nginx sendet content-security-policy sowieso bereits kleingeschrieben auf dem Draht — egal wie Sie es in der Config schreiben — also ist das selten ein Problem. Aber falls Sie mit Tools debuggen die Header wörtlich darstellen, lassen Sie sich nicht vom Case-Unterschied verwirren.
Fortgeschritten: map für bedingte Policies
Wenn Sie unterschiedliche CSPs für verschiedene Teile Ihrer Seite wollen — etwa strikt auf den Marketing-Seiten, lockerer im Admin-Panel — nutzen Sie eine map-Direktive im http { }-Kontext:
map $request_uri $csp_policy {
default "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'";
~^/admin/ "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'";
}
server {
add_header Content-Security-Policy $csp_policy always;
}
So bleibt ein einziger add_header-Aufruf bestehen und die passende Policy wird auf den jeweiligen Pfad geroutet.