Security & Anonymity

Content Security Policy (CSP) for Site Owners – Practical Setup

CSP tells browsers which scripts and resources are allowed - the policy directives, how to write one without breaking your site, and report-only mode.

5 min read

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

DirectiveWhat it controls
default-srcFallback for any not-specified type
script-srcJavaScript sources
style-srcCSS sources
img-srcImage sources
font-srcFont sources
connect-srcfetch/XHR/WebSocket destinations
frame-srciframe sources
media-srcVideo/audio sources
object-src<object>, <embed> sources
base-uriAllowed values for <base> tag
form-actionForm submission destinations
frame-ancestorsWho 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:

  1. Deploy strict CSP in report-only mode.
  2. Wait days. Collect violation reports.
  3. Each violation = legitimate need? Add to policy. Malicious? Confirms CSP works.
  4. Tighten policy.
  5. 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.

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

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?