EN DE

Content Security Policy in Nginx

Eine CSP in Nginx zu deployen ist üblicherweise ein Einzeiler in deinem 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

Öffne die Konfigurationsdatei deiner Site (auf Debian/Ubuntu meist /etc/nginx/sites-enabled/deine-seite, auf RHEL/CentOS oft /etc/nginx/conf.d/deine-seite.conf) und füge 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 deine 404-Not-Found-, 403-Forbidden- und 500-Internal-Server-Error-Seiten haben gar keinen CSP-Header. Solche Fehlerseiten spiegeln oft benutzerkontrollierten Inhalt zurück (denk 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

Deploye den Header zuerst als Content-Security-Policy-Report-Only. Der Browser protokolliert Verstöße in der Konsole, blockiert aber nichts. So kannst du 1–2 Wochen beobachten und die Warnungen aufräumen, bevor du zum Durchsetzen wechselst. 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 deine 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.

Ohne Trial and Error: Unser Scanner crawlt deine Seite und gibt dir die exakte CSP die du brauchst, mit allen nötigen Origins schon freigegeben. Copy-Paste in Nginx, fertig.

Typische Fehlerquellen

Verschachtelte add_header-Direktiven löschen sich gegenseitig

Diese Falle erwischt jeden mindestens einmal. Wenn du add_header sowohl im server-Block UND in einem verschachtelten location-Block hast, 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.
        # Du musst 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://deine-seite.example verifizieren. Wenn die CSP im Output fehlt, prüfe:

Mehrere CSP-Header gleichzeitig

Wenn du mehrere add_header Content-Security-Policy ...-Direktiven hast (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 du nicht erwartet hast. 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 du es in der Config schreibst — also ist das selten ein Problem. Aber falls du mit Tools debuggst die Header wörtlich darstellen, lass dich nicht vom Case-Unterschied verwirren.

Fortgeschritten: map für bedingte Policies

Wenn du unterschiedliche CSPs für verschiedene Teile deiner Seite willst — etwa strikt auf den Marketing-Seiten, lockerer im Admin-Panel — nutze 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.

Rumraten überspringen — jetzt scannen → Die exakte CSP für Nginx in 30 Sekunden, mit jeder externen Ressource die deine Seite tatsächlich lädt bereits freigegeben