Content Security Policy in Apache (.htaccess)
Eine Content Security Policy schränkt ein, welche Ressourcen der Browser auf deiner 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 apache2
Auf 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.so
Apache danach neu starten. Dass das Modul wirklich geladen ist, kannst du mit apachectl -M | grep headers prüfen.
Das minimale .htaccess-Snippet
Öffne die .htaccess-Datei im Document-Root (oder leg sie an, falls sie nicht existiert) und füge 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 deine 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.
Testen
Seite neu laden, DevTools (F12) → Konsole. Du siehst 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 deine Seite lädt und die die aktuelle Policy nicht abdeckt. Du hast 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, änderst du den Header-Namen von Content-Security-Policy-Report-Only zu Content-Security-Policy und lädst 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. Deine 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, hast du 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üfe, 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://deine-seite.example siehst du 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 deine Seite beim Laden einer Ressource blockiert wird, kann das CORS (Access-Control-Allow-Origin) sein und nicht CSP. CSP regelt was deine Seite laden darf; CORS regelt, ob der andere Server bereit ist, von deiner 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 deine 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.
Apache-spezifische Feinheiten
.htaccess und Performance
Jede Direktive in der .htaccess wird bei jedem Request neu ausgewertet. Für ein einzelnes Header set ist der Overhead vernachlässigbar, aber wenn du maximale Performance willst, verschiebst du die Direktive in den VirtualHost-Block unter /etc/apache2/sites-enabled/deine-seite.conf und deaktivierst .htaccess komplett mit AllowOverride None. Gleiche Syntax, kein Overhead pro Request.
Header append vs. Header set
Nimm immer set, niemals append. append würde mehrere CSP-Werte aneinanderhängen, und Browser behandeln das als Schnittmenge — in der Regel nicht das was du willst.
Reporting-Endpoints
Sobald du im Enforcing-Modus bist, lohnt sich eine report-uri-Direktive, damit du Verstöße siehst die draußen in freier Wildbahn passieren (nicht nur bei deinen eigenen Tests):
Header always set Content-Security-Policy "default-src 'self'; ... ; report-uri https://dein-reporting-endpunkt.example/csp"
Dienste wie report-uri.com bieten kostenlose Stufen; du kannst aber auch selber einen Collector hosten.
Deine exakte CSP in 30 Sekunden generieren → Wir crawlen deine Seite, finden jede Ressource, und spucken einen .htaccess-Block aus der zu deinem echten Stack passt — kein Rumraten