Content Security Policy (CSP) tells the browser “only load scripts, images, and other resources from these specific sources.” Block list for everything else. CSP is one of the most effective defenses against cross-site scripting (XSS) and other injection attacks — but it’s also one of the most likely to break your site if configured carelessly. This guide covers what CSP does, how to write a working policy, and the “report-only” mode for safe testing.
What CSP does
Server sends a header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.google-analytics.com
Browser enforces:
- Default sources must be same-origin.
- Scripts allowed only from same-origin or google-analytics.com.
- Anything else (inline scripts, third-party scripts, eval()) is blocked.
Attacker injects a script tag pointing to evil.com? Browser refuses to load it. CSP makes XSS payloads much harder to deliver.
CSP directives explained
| Directive | What it controls |
|---|---|
default-src | Fallback for any not-specified type |
script-src | JavaScript sources |
style-src | CSS sources |
img-src | Image sources |
font-src | Font sources |
connect-src | fetch/XHR/WebSocket destinations |
frame-src | iframe sources |
media-src | Video/audio sources |
object-src | <object>, <embed> sources |
base-uri | Allowed values for <base> tag |
form-action | Form submission destinations |
frame-ancestors | Who can embed your site in iframes |
Source value syntax
- ‘self’ — Same scheme/host/port as page.
- ‘none’ — Nothing allowed.
- ‘unsafe-inline’ — Allow inline <script> and inline style attributes. AVOID where possible.
- ‘unsafe-eval’ — Allow eval(), Function(), setTimeout(string). AVOID.
- https://example.com — Specific host.
- *.example.com — Wildcard subdomain.
- data: — data: URLs (for inlined images).
- ‘nonce-RANDOM’ — Allow scripts/styles with matching nonce attribute. Best practice for inline.
- ‘sha256-HASH’ — Allow scripts/styles matching specific hash.
Building a policy for WordPress
WordPress sites use many third-party resources by default. A starting CSP that allows the common ones:
default-src 'self';
script-src 'self' 'unsafe-inline' https://www.google-analytics.com https://www.googletagmanager.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https: blob:;
font-src 'self' data: https://fonts.gstatic.com;
connect-src 'self' https://www.google-analytics.com;
frame-ancestors 'self';
base-uri 'self';
form-action 'self';
This is “permissive but real” — allows what most WordPress sites need. Tightening ‘unsafe-inline’ requires careful work; many themes and plugins rely on inline scripts.
Report-only mode for safe testing
Before deploying CSP for real, deploy in report-only mode. Browser reports violations but doesn’t block anything. You see what your policy would have blocked.
Content-Security-Policy-Report-Only: default-src 'self'; report-to csp-endpoint;
Add report-to or report-uri directive pointing to a reporting endpoint that collects violation reports. Services like report-uri.com or Sentry handle this.
Workflow:
- Deploy strict CSP in report-only mode.
- Wait days. Collect violation reports.
- Each violation = legitimate need? Add to policy. Malicious? Confirms CSP works.
- Tighten policy.
- Once violations are zero for a week, switch from Content-Security-Policy-Report-Only to Content-Security-Policy. Now actually enforcing.
Implementing in .htaccess
<IfModule mod_headers.c>
Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; frame-ancestors 'self';"
</IfModule>
Single line — no line breaks within the header value.
Common breaks
“Inline script blocked”
Theme has <script>document.addEventListener(...)</script> in source. Without ‘unsafe-inline’ or a nonce, blocked.
Options:
- Allow ‘unsafe-inline’ (weakens CSP).
- Move inline scripts to external files.
- Add nonces dynamically (complex).
“Google Fonts not loading”
Add fonts.googleapis.com to style-src and fonts.gstatic.com to font-src.
“YouTube embeds blocked”
Add youtube.com and www.youtube.com to frame-src.
“Form submission to external service blocked”
Add destination to form-action directive.
“Visual editor stops working”
TinyMCE / Gutenberg use eval() and inline scripts heavily. May need ‘unsafe-eval’ for admin. Better: separate CSP for /wp-admin/ vs front-end.
Different CSP for admin vs frontend
WordPress admin needs much more permissive policy than visitor-facing pages. Achievable via .htaccess location-specific rules:
<Location /wp-admin>
Header set Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; img-src * data: blob:;"
</Location>
Strict CSP for visitor pages; relaxed for admin where you (the trusted user) are the only one accessing.
CSP-related headers
X-Frame-Options (legacy)
Older clickjacking protection. CSP frame-ancestors directive supersedes it but both are commonly set:
Header set X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options
Header set X-Content-Type-Options "nosniff"
Prevents MIME type confusion attacks. No reason not to set this.
Tools and resources
- Mozilla Observatory (observatory.mozilla.org) — Grades CSP and other security headers.
- CSP Evaluator (csp-evaluator.withgoogle.com) — Google’s tool for evaluating policy strength.
- Browser DevTools Console — Shows CSP violations as you browse.
- Report URI (report-uri.com) — Free tier for collecting CSP violation reports.
Common questions
“Is CSP necessary if I have other security in place?” CSP is defense in depth. ModSecurity, Imunify360, secure code — CSP layers on top, mitigating mistakes those tools miss.
“My PageSpeed score is poor and CSP is mentioned.” Unrelated typically. CSP doesn’t affect performance directly.
“I have CSP and Cloudflare adds its own — conflict?” Two CSP headers both apply — browser uses INTERSECTION (more restrictive). Usually fine but may cause confusing breaks.
“My CSP is huge and ugly — is that OK?” Yes. Complex sites with many integrations need lengthy policies. Readability is for you; browser doesn’t care.
“WordPress plugin claims to handle CSP for me.” Some do; they often default to permissive policies that aren’t much stronger than no CSP. Custom .htaccess gives precise control.
What’s next
- HSTS for HTTPS enforcement: HSTS.
- htaccess security: .htaccess essentials.
- WordPress hardening: Hardening.
CSP is the most effective single header for XSS defense — when configured correctly. Configured carelessly, it breaks legitimate site functionality. Use report-only mode for weeks before enforcing; tighten gradually; accept that complex sites need permissive policies. Even imperfect CSP is much better than no CSP for any site handling user input or sensitive data.
Was this helpful?
Thanks for your feedback!