
Wir haben euch zu diesem Thema zwei Checklisten gebaut! 💙📋
Checkliste für CSP bei WordPress / Joomla
Checkliste für CSP bei React (Next.js etc.)
Wer eine Website betreibt, hat meistens schon von CSP gehört – oft im Zusammenhang mit Fehlermeldungen in der Browser-Konsole wie Refused to load script from 'https://...' because it violates the following Content Security Policy directive. Aber was steckt eigentlich hinter CSP, warum ist es wichtig, und wie setzt man es sinnvoll um – ohne dabei die halbe Website zu brechen?
Dieser Artikel erklärt CSP von Grund auf: Was die einzelnen Direktiven bedeuten, wie Nonces funktionieren, und wie man CSP in Plesk, Nginx, Next.js und WordPress einsetzt.
Was ist eine Content Security Policy?
Eine Content Security Policy ist ein HTTP-Response-Header, den der Server zusammen mit einer Webseite ausliefert. Er teilt dem Browser mit: Was darf diese Seite laden – und was nicht?
Der Browser liest diesen Header, bevor er irgendetwas rendert, und blockiert alles, was nicht explizit erlaubt ist. Das klingt simpel, hat aber eine enorme Sicherheitswirkung.
Der Header heißt schlicht:
Content-Security-Policy: ...
Alternativ kann man eine CSP auch über ein <meta>-Tag im HTML-Head definieren – das ist aber weniger mächtig, weil manche Direktiven dort nicht funktionieren und der Browser das HTML erst parsen muss, bevor er die Policy kennt. Der Header-Ansatz ist klar vorzuziehen.
Warum braucht man CSP überhaupt?
Die häufigste Angriffsfläche, gegen die CSP schützt, ist Cross-Site Scripting (XSS). Dabei schafft es ein Angreifer, schadhaften JavaScript-Code in eine Website einzuschleusen – zum Beispiel über ein Kommentarfeld, URL-Parameter oder eine unsichere Datenbank. Ohne CSP führt der Browser diesen Code einfach aus, weil er nicht weiß, ob er legitim ist oder nicht.
Mit einer CSP sagst du dem Browser: Führe nur Scripts aus, die von meinedomain.de kommen – alles andere blockierst du. Selbst wenn ein Angreifer Code einschleust, läuft dieser nicht.
CSP schützt außerdem vor:
- Clickjacking – also dem Einbetten deiner Seite in einen unsichtbaren iframe auf einer fremden Domain
- Mixed Content – wenn eine HTTPS-Seite unsichere HTTP-Ressourcen lädt
- Daten-Exfiltration – wenn injizierter Code versucht, Daten an eine externe URL zu schicken
- Formular-Hijacking – wenn Formulare an fremde Adressen abgeschickt werden
Wie ist eine CSP aufgebaut?
Eine CSP besteht aus mehreren Direktiven, die durch Semikolons getrennt sind. Jede Direktive definiert eine bestimmte Art von Ressource und legt fest, woher sie geladen werden darf.
Content-Security-Policy: default-src 'self'; script-src 'self' cdn.example.com; img-src *;
Dieser Einzeiler sagt: Standardmäßig nur von der eigenen Domain laden. Scripts dürfen zusätzlich von cdn.example.com kommen. Bilder dürfen von überall geladen werden.
Schauen wir uns die wichtigsten Direktiven im Detail an.
Die wichtigsten Direktiven erklärt
default-src
Das ist die Fallback-Direktive. Wenn für einen bestimmten Ressourcentyp keine eigene Direktive definiert ist, greift default-src. Man setzt sie meistens auf 'self' – also nur die eigene Domain – und öffnet dann gezielt nur das, was wirklich gebraucht wird.
Faustregel: default-src 'self' als Ausgangspunkt, dann von dort aus erweitern.
script-src
Steuert, woher JavaScript-Dateien und Inline-Scripts geladen bzw. ausgeführt werden dürfen. Das ist die kritischste Direktive, weil sie direkt über XSS-Schutz entscheidet.
Mögliche Werte:
'self'– nur von der eigenen Domain'none'– gar kein JavaScript'unsafe-inline'– erlaubt Inline-Scripts wie<script>alert()</script>– sollte vermieden werden'unsafe-eval'– erlaubteval()und ähnliche Funktionen – ebenfalls gefährlichhttps://cdn.example.com– eine konkrete externe Domain'nonce-xyz123'– nur Scripts mit dem passenden Nonce-Attribut (dazu gleich mehr)
Das Ziel ist es, unsafe-inline und unsafe-eval zu vermeiden. Viele Frameworks erzwingen das leider durch ihre Build-Mechanismen – deshalb braucht man Nonces oder hash-basierte Freigaben.
style-src
Dasselbe wie script-src, aber für CSS. Auch hier gibt es 'unsafe-inline' – und auch hier sollte man es möglichst vermeiden, obwohl das Risiko bei CSS geringer ist als bei JavaScript.
Wer Google Fonts einbindet, muss https://fonts.googleapis.com hier freigeben.
img-src
Definiert, woher Bilder geladen werden dürfen. Hier ist * (also überall) oft tolerierbar, da Bilder kein JavaScript ausführen können. Wer viele externe Dienste nutzt (CDNs, Analytics-Pixel, Social-Media-Embeds), muss hier entsprechend viele Domains eintragen.
Wichtig: data: muss separat freigegeben werden, wenn man Base64-Bilder verwendet.
connect-src
Steuert, wohin JavaScript-Requests gehen dürfen – also fetch(), XMLHttpRequest, WebSockets und navigator.sendBeacon(). Das ist besonders relevant für:
- API-Anfragen an eigene oder fremde Backends
- Analytics-Endpunkte (Matomo, Plausible, etc.)
- WebSocket-Verbindungen
- Sentry oder andere Error-Tracking-Dienste
Wer Matomo selbst hostet, muss hier die eigene Matomo-Domain freigeben.
font-src
Woher Schriften geladen werden dürfen. Google Fonts lädt die eigentlichen Font-Dateien von https://fonts.gstatic.com – das ist ein häufig vergessener Eintrag, wenn man Google Fonts in Kombination mit style-src einschränkt.
frame-src und frame-ancestors
Zwei verschiedene Direktiven mit ähnlichem Klang, aber unterschiedlicher Wirkung:
frame-src– steuert, welche externen Seiten als<iframe>in der eigenen Seite eingebettet werden dürfen (z. B. YouTube-Videos, Google Maps).frame-ancestors– steuert, wer die eigene Seite als<iframe>einbetten darf. Mit'none'verhindert man Clickjacking vollständig.
frame-ancestors ist der moderne Ersatz für den X-Frame-Options-Header.
form-action
Gibt an, wohin <form>-Formulare abgeschickt werden dürfen. Mit 'self' verhindert man, dass injizierter Code Formulare an fremde Server schickt.
base-uri
Steuert, welche URLs in einem <base>-Tag erlaubt sind. Klingt nischig, ist aber wichtig: Ein Angreifer könnte über ein injiziertes <base>-Tag alle relativen Links auf der Seite umleiten. 'self' oder 'none' schützt davor.
upgrade-insecure-requests
Kein Quell-Filter, sondern eine Anweisung: Lade alle HTTP-Ressourcen automatisch als HTTPS. Praktisch für ältere Websites, bei denen noch nicht alle internen Links auf HTTPS migriert wurden.
object-src
Steuert <object>, <embed> und <applet> – also vor allem Flash und Plugins. In modernen Websites fast immer 'none', da diese Technologien obsolet sind.
Nonce-basierte CSP: Der elegante Weg für dynamische Scripts
Das größte Problem mit CSP in der Praxis: Viele Frameworks und Plugins brauchen Inline-Scripts. Und 'unsafe-inline' zu erlauben hebt den XSS-Schutz größtenteils auf.
Die Lösung heißt Nonce (Number Used Once). Das Prinzip:
- Der Server generiert bei jedem Request einen neuen, zufälligen Wert – den Nonce.
- Dieser Wert landet als
nim<script>-Tag. - Und gleichzeitig in der CSP:
script-src 'nonce-abc123'. - Der Browser führt nur Scripts aus, die genau diesen Nonce tragen.
<script n>
// Dieser Script wird ausgeführt – Nonce stimmt überein
</script>
<script>
// Dieser nicht – kein Nonce vorhanden
</script>
Der entscheidende Punkt: Weil der Nonce bei jedem Request neu generiert wird und ein Angreifer ihn nicht vorhersagen kann, kann injizierter Code den Nonce nicht setzen – und wird damit blockiert.
Nonces funktionieren nur serverseitig. Wer statische Dateien ausliefert, muss zu hash-basierten Freigaben greifen: Dabei wird ein SHA-256-Hash des genauen Inline-Script-Inhalts berechnet und in der CSP angegeben – der Browser vergleicht dann selbst.
CSP in der Praxis: Wo und wie setzt man sie ein?
Plesk / Apache / .htaccess
In Plesk-Umgebungen mit Apache lässt sich der Header bequem über die .htaccess-Datei setzen – eine einzige Zeile reicht:
Header set Content-Security-Policy "default-src 'self'; img-src *;"
Noch sauberer ist es, den Header direkt in Plesk unter Domains → Apache & nginx Einstellungen → Zusätzliche Direktiven einzutragen. Das hat den Vorteil, dass die Einstellung nicht durch eine .htaccess weiter unten überschrieben werden kann.
Wichtig: Das Apache-Modul mod_headers muss aktiviert sein – in den meisten Plesk-Installationen ist das standardmäßig der Fall.
Nginx
In einer Nginx-Konfiguration gehört der Header in den server– oder location-Block. Wer Plesk nutzt, kann das bequem unter Zusätzliche Nginx-Direktiven eintragen, ohne Konfigurationsdateien manuell zu editieren:
add_header Content-Security-Policy "default-src 'self';";
Wer Nonces braucht, muss beachten: Nginx selbst generiert keine dynamischen Werte. Das übernimmt dann die Anwendung dahinter – Next.js, PHP oder ein anderes Backend – und Nginx liefert den Header einfach weiter.
Next.js
In Next.js gibt es zwei saubere Wege:
Weg 1 – Statische Header über next.config.js:
Über den headers()-Callback lassen sich CSP-Header für alle oder bestimmte Routen definieren. Das ist der einfachste Weg für Apps, die keine Nonces brauchen – zum Beispiel weil man komplett auf Inline-Scripts verzichtet oder externe Scripts über <Script strategy="beforeInteractive"> lädt.
Weg 2 – Middleware mit dynamischen Nonces:
Die middleware.ts-Datei läuft bei jedem Request auf dem Edge-Layer. Hier generiert man den Nonce, setzt ihn im Response-Header und gibt ihn über eine Header-Variable an die layout.tsx weiter, wo er in die <script>-Tags eingesetzt wird.
Das ist der empfohlene Weg für Next.js-Apps mit serverseitigem Rendering – und der einzige, der echten Nonce-Schutz bietet.
Ein häufiger Stolperstein: Next.js selbst injiziert eigene Inline-Scripts für die Hydration. Diese müssen entweder über den Nonce freigegeben werden, oder man nutzt 'strict-dynamic' in Kombination mit dem Nonce. 'strict-dynamic' bedeutet: Scripts, die von einem vertrauenswürdigen (nonce-freigegebenen) Script geladen werden, sind automatisch ebenfalls erlaubt – ohne sie einzeln auflisten zu müssen.
WordPress und andere CMS
WordPress ist in Sachen CSP eine besondere Herausforderung, weil Plugins fröhlich Inline-Scripts und externe Ressourcen einbinden – oft ohne Dokumentation. Eine restriktive CSP bricht hier schnell halbe Funktionalitäten.
Die empfohlene Vorgehensweise:
1. Erst im Report-Only-Modus starten und schauen, was geblockt würde – mehr dazu im nächsten Abschnitt.
2. Plugins reduzieren – jedes Plugin, das man entfernt, ist eine Quelle weniger, die man in der CSP freigeben muss.
3. CSP über ein Plugin setzen – Tools wie WP Headers and Footers oder HTTP Security Headers erlauben es, Header direkt aus dem WordPress-Backend zu verwalten, ohne die Server-Konfiguration anzufassen.
4. Inline-Scripts inventarisieren – viele WordPress-Themes und Plugins brauchen 'unsafe-inline'. Wer das vermeiden will, kann Nonces über den wp_scripts_add_data()-Filter integrieren – das ist aber Entwicklungsarbeit.
Für einfache Marketing-Websites ist eine moderate CSP mit 'unsafe-inline' für Scripts und Styles oft der pragmatische Kompromiss. Für Login-Seiten, Shops oder Backends lohnt die striktere Variante.
Report-Only: CSP ohne Risiko testen
Bevor man eine CSP live schaltet, die möglicherweise die halbe Seite bricht, gibt es einen sicheren Weg: Content-Security-Policy-Report-Only.
Dieser Header funktioniert identisch zur normalen CSP – mit einem Unterschied: Der Browser blockiert nichts, er meldet nur. Verstöße werden an einen definierten Endpunkt gesendet:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-violations;
So kann man die Policy über Tage oder Wochen beobachten, alle Violations sammeln, die Policy anpassen – und erst dann auf den echten Header wechseln, wenn man sicher ist, dass nichts bricht.
Wer keinen eigenen Report-Endpunkt betreiben will, kann Dienste wie Report URI nutzen, die Violations übersichtlich aufbereiten und aggregieren.
Häufige Fehler und wie man sie vermeidet
Zu weit öffnen statt gezielt freizugebenscript-src * erlaubt JavaScript von jeder Domain – damit ist die Policy für XSS-Schutz wertlos. Immer konkrete Domains, Nonces oder Hashes verwenden.
Wildcards auf Subdomains*.example.com klingt praktisch, öffnet aber jede Subdomain. Wenn eine Subdomain kompromittiert wird, kann von dort aus schädlicher Code geladen werden.
unsafe-inline als Schnelllösung
Es ist verlockend, 'unsafe-inline' einzutragen, weil irgendetwas nicht funktioniert. Aber damit verliert script-src fast seinen gesamten Sicherheitswert. Besser: den Inline-Script identifizieren und per Nonce oder Hash freigeben.
CSP nur im Dev-Modus testen
Development-Builds von React, Vue & Co. enthalten deutlich mehr Inline-Scripts und eval()-Aufrufe als Production-Builds. Immer gegen den Production-Build testen.
Vergessen, dass CSP per Seite greift
CSP gilt für die Seite, auf der sie ausgeliefert wird – nicht global für alle externen Ressourcen, die diese Seite lädt. Wer Iframes einbettet, beeinflusst damit nicht die CSP innerhalb des Iframes.
Tools zum Testen und Validieren
- CSP Evaluator von Google – analysiert eine CSP auf Schwachstellen und gibt konkrete Hinweise
- securityheaders.com – prüft alle Sicherheits-Header einer Domain auf einmal und bewertet sie mit einer Schulnote
- Report URI – managed Reporting-Endpunkt für CSP-Violations
- Browser DevTools – die Konsole zeigt jeden CSP-Verstoß mit der genauen Direktive und der betroffenen Ressource
Fazit
CSP ist kein Allheilmittel – aber ein extrem wirksames Werkzeug, das oft unterschätzt wird. Richtig eingesetzt reduziert es das XSS-Risiko drastisch, verhindert Clickjacking und gibt dir Kontrolle darüber, was deine Seite wirklich nach außen kommuniziert.
Die wichtigsten Takeaways:
- Starte mit
Report-Only– niemals blind live schalten default-src 'self'als sichere Basis, dann gezielt öffnen- Nonces statt
unsafe-inline– vor allem in Next.js und modernen SPAs - WordPress braucht Geduld – Plugin-Inventur vor CSP-Rollout
- CSP ist iterativ – kein System hat von Anfang an die perfekte Policy
Wer CSP konsequent umsetzt, hat einen echten Sicherheitsvorteil gegenüber dem Gros der Websites – und das mit verhältnismäßig geringem Aufwand.
Hast du Fragen zu CSP in deinem konkreten Setup? Schreib uns gerne – oder wirf einen Blick auf unsere weiteren Security- und Server-Tutorials im Blog.
Disclaimer: Dieser Artikel dient ausschließlich allgemeinen Informationszwecken und ersetzt keine Rechtsberatung. Für verbindliche Auskünfte empfehlen wir die Konsultation einer spezialisierten Anwältin oder eines spezialisierten Datenschutzjuristen.