Content Security Policy in Apache (.htaccess)
Eine Content Security Policy schränkt ein, welche Ressourcen der Browser auf Ihrer Apache-Website laden darf. Diese Anleitung zeigt das minimale, funktionierende Deployment: mod_headers aktivieren, Header in .htaccess eintragen, im Report-Only-Modus testen, dann auf Enforcing umschalten. Fünf Minuten Ende-zu-Ende, sobald mod_headers läuft.
Voraussetzung: mod_headers aktivieren
Apaches mod_headers-Modul ist das was der .htaccess erlaubt, Response-Header zu setzen. Auf Debian/Ubuntu-Derivaten:
sudo a2enmod headers
sudo systemctl restart apache2Auf RHEL/CentOS/Fedora die /etc/httpd/conf/httpd.conf öffnen und sicherstellen, dass diese Zeile vorhanden und nicht auskommentiert ist:
LoadModule headers_module modules/mod_headers.soApache danach neu starten. Mit apachectl -M | grep headers prüfen Sie, ob das Modul wirklich geladen ist.
Das minimale .htaccess-Snippet
Öffnen Sie die .htaccess-Datei im Document-Root (oder legen Sie sie an, falls sie nicht existiert) und fügen Sie Folgendes ein:
<IfModule mod_headers.c>
Header always set 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'"
</IfModule>Ein paar Dinge zum Merken:
<IfModule mod_headers.c>schützt die Direktive davor, dass Apache bricht falls das Modul doch nicht geladen ist.Header always set— dasalways-Flag ist kritisch. Ohne es setzt Apache den Header nur auf 2xx-Responses. Heisst Ihre 404- und 500-Fehlerseiten haben gar keine CSP — und das sind genau die Seiten auf denen häufig ein XSS-Loch lauert, weil dort benutzerkontrollierter Content zurückgespiegelt wird.Content-Security-Policy-Report-Only— der Browser protokolliert Verstöße in der Konsole, blockiert aber nichts. Der sichere Weg zum Rollout.
Report-Only: testen ohne etwas kaputtzumachen
Der Unterschied zwischen den beiden Header-Namen ist der einzige, auf den es beim Rollout ankommt:
# meldet nur — der Browser lädt alles, protokolliert aber jeden Verstoß
Header always set Content-Security-Policy-Report-Only "..."
# setzt durch — der Browser blockiert, was die Policy nicht erlaubt
Header always set Content-Security-Policy "..."Die Policy-Syntax ist in beiden Fällen identisch. Sie können also eine Policy wochenlang im Report-Only-Modus beobachten und den Header-Namen erst ändern, wenn die Meldungen ausbleiben. Kein Besucher merkt in dieser Zeit etwas davon.
Beide Header gleichzeitig senden
Weniger bekannt, aber der eigentliche Grund, warum Report-Only mehr ist als eine Testphase: Apache darf beide Header setzen, und der Browser wertet sie unabhängig voneinander aus.
<IfModule mod_headers.c>
# läuft seit Monaten, bleibt scharf
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'"
# der Kandidat: ohne 'unsafe-inline', vorerst nur beobachtend
Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'"
</IfModule>Das ist der übliche Weg, eine bestehende Policy zu verschärfen: Die scharfe Policy schützt weiter, während die strengere daneben zeigt, was beim Umstellen brechen würde. Bleibt die Konsole bei der Report-Only-Variante über mehrere Wochen ruhig, tauschen Sie sie gegen die alte aus und löschen die Report-Only-Zeile.
Die Konsolenausgabe lesen
Seite neu laden, DevTools (F12) → Konsole. Sie sehen rote Warnungen für jede Ressource die blockiert werden würde, z. B.:
[Report Only] Refused to load the script 'https://cdn.example.com/analytics.js' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline'".Jede dieser Zeilen ist eine echte Ressource die Ihre Seite lädt und die die aktuelle Policy nicht abdeckt. Sie haben zwei Optionen: Domain in die passende Direktive aufnehmen, oder die Abhängigkeit loswerden. Iterieren bis die Konsole sauber ist.
.htaccess einfügen.In den Enforcing-Modus wechseln
Sobald die Konsole ruhig bleibt, ändern Sie den Header-Namen von Content-Security-Policy-Report-Only zu Content-Security-Policy und laden neu:
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'"
</IfModule>Fertig. Ihre Seite setzt jetzt eine CSP durch. Ab diesem Moment wird jeder Verstoß vom Browser wirklich blockiert, nicht nur gemeldet.
Typische Fehlerquellen
„mod_headers is not loaded"
Wenn das im Apache-Error-Log auftaucht, haben Sie die Voraussetzung übersprungen. sudo a2enmod headers && sudo systemctl restart apache2 (Debian/Ubuntu) oder die LoadModule-Zeile in der httpd.conf auskommentieren.
Der Header ist da, aber die Policy wird ignoriert
Prüfen Sie, ob an einer anderen Stelle ein zweiter CSP-Header gesetzt wird (von PHP, einer VirtualHost-Datei, einer anderen .htaccess). Wenn mehrere Content-Security-Policy-Header gesendet werden, nehmen Browser die Schnittmenge — und die ist oft restriktiver als beabsichtigt. Mit curl -I https://ihre-seite.example sehen Sie alle Header. Duplikate eliminieren und nur den einen aus .htaccess behalten.
WordPress/PHP-Anwendungen brauchen 'unsafe-inline'
Die meisten CMS und PHP-Frameworks erzeugen Inline-<script>- und <style>-Blöcke. Ohne 'unsafe-inline' in script-src und style-src bricht die Seite. Die striktere Alternative wären Nonces oder Hashes, aber das erfordert Code-Anpassungen — Details in der WordPress-Anleitung.
CORS und CSP sind zwei verschiedene Dinge
Ein häufiges Missverständnis: Wenn Ihre Seite beim Laden einer Ressource blockiert wird, kann das CORS (Access-Control-Allow-Origin) sein und nicht CSP. CSP regelt was Ihre Seite laden darf; CORS regelt, ob der andere Server bereit ist, von Ihrer Seite aus geladen zu werden. Beide können Ressourcen blockieren, die Lösung ist jeweils eine andere.
Dev-Server mit Hot-Reload
Webpack Dev Server, Vite, Next.js im Dev-Modus — die injizieren alle Inline-Skripte für Hot Reloading. Wenn Ihre CSP das blockiert, bricht die Dev-Umgebung zusammen. Lösung: die strikte CSP nur in Produktion ausrollen. In Apache lässt sich das über SetEnvIf lösen, oder einfach indem die .htaccess mit der CSP nur in Produktion deployt wird.
VirtualHost statt .htaccess
Wer Root-Zugriff auf den Server hat, arbeitet nicht in der .htaccess, sondern im VirtualHost. Die Syntax ist dieselbe, nur der Ort ist ein anderer:
<VirtualHost *:443>
ServerName ihre-seite.example
DocumentRoot /var/www/ihre-seite/public
<IfModule mod_headers.c>
Header always set 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'"
</IfModule>
</VirtualHost>Wo diese Datei liegt, hängt von der Distribution ab:
- Debian und Ubuntu:
/etc/apache2/sites-available/ihre-seite.conf, aktiviert übera2ensite, neu geladen mitsudo systemctl reload apache2. - RHEL, Fedora und CentOS:
/etc/httpd/conf.d/ihre-seite.conf, neu geladen mitsudo systemctl reload httpd.
Der eine Unterschied, der im Alltag zählt: Eine Änderung an der .htaccess wirkt beim nächsten Request, eine Änderung am VirtualHost erst nach einem Reload. Das macht die .htaccess zum besseren Ort während der Testphase und den VirtualHost zum besseren, sobald die Policy steht — dort kann kein versehentlich hochgeladenes Verzeichnis sie überschreiben.
Prüfen Sie vor jedem Reload mit sudo apachectl configtest, ob die Datei fehlerfrei ist. Ein Syntaxfehler im VirtualHost verhindert den Start des gesamten Servers — anders als in der .htaccess, wo er nur einen 500er für dieses eine Verzeichnis auslöst.
Apache-spezifische Feinheiten
.htaccess und Performance
Jede Direktive in der .htaccess wird bei jedem Request neu ausgewertet — Apache sucht dabei in jedem Verzeichnis entlang des Pfades nach einer solchen Datei. Für ein einzelnes Header set ist dieser Aufwand nicht messbar; wer die .htaccess deswegen meidet, optimiert an der falschen Stelle. Spürbar wird es erst bei umfangreichen Rewrite-Regeln auf Seiten mit hoher Last. Dann lohnt sich der Umzug in den VirtualHost und AllowOverride None, was die Suche ganz abschaltet.
Header append vs. Header set
Nehmen Sie immer set, niemals append. append würde mehrere CSP-Werte aneinanderhängen, und Browser behandeln das als Schnittmenge — in der Regel nicht das was Sie wollen.
Reporting-Endpoints
Sobald Sie im Enforcing-Modus sind, lohnt sich eine report-uri-Direktive, damit Sie Verstöße sehen die draußen in freier Wildbahn passieren (nicht nur bei Ihren eigenen Tests):
Header always set Content-Security-Policy "default-src 'self'; ... ; report-uri https://ihr-reporting-endpunkt.example/csp"Dienste wie report-uri.com bieten kostenlose Stufen; Sie können aber auch selber einen Collector hosten.
Ihre exakte CSP in 30 Sekunden generieren → Wir crawlen Ihre Seite, finden jede Ressource, und spucken einen .htaccess-Block aus der zu Ihrem echten Stack passt — kein Rumraten