CSP Report-Only vs Enforce: how to roll out a Content-Security-Policy safely

You want a real Content-Security-Policy, but flipping it on overnight breaks half the site. Report-Only lets the browser complain without blocking, so you can fix the noise first.

The two headers

HeaderBlocks?Reports?Use for
Content-Security-PolicyYesYes (if report-uri / report-to set)The live policy visitors must obey
Content-Security-Policy-Report-OnlyNoYesTrying a stricter draft without breaking pages

Both accept the same directive syntax. A meta tag can also be report-only: <meta http-equiv="Content-Security-Policy-Report-Only" content="…">. Remember that frame-ancestors, report-uri and sandbox are ignored in meta tags — send those as real headers.

Safe rollout pattern

  1. Draft. Write the policy you want (start with default-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'self' and add the CDNs you really need). Paste it into the CSP header explainer to catch unquoted keywords, missing semicolons and weak spots.
  2. Report-Only first. Ship only Content-Security-Policy-Report-Only for a few days. Watch the browser console and your report endpoint; fix every legitimate violation (move inline scripts, add nonces/hashes, allow the right hosts).
  3. Enforce the known-good policy. Flip the same string to Content-Security-Policy.
  4. Tighten with a second header. Keep the enforcing policy, and add a stricter Report-Only draft beside it. When that draft is quiet, promote it.
# nginx example: enforce a safe baseline, report on a stricter draft
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'; report-uri /csp-report" always;
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'nonce-$request_id' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; report-uri /csp-report" always;

Where reports go

A report names the document URI, the violated directive, the blocked URL (or inline), and sometimes the sample of the blocked script. Treat report bodies as untrusted input: they can contain attacker-controlled URLs.

What Report-Only cannot do

Related

CSP header explainer · Refused to execute inline script · Refused to connect (connect-src) · Trusted Types · Anvil CSP checker API

Questions

What is Content-Security-Policy-Report-Only?

A header with the same syntax as CSP that logs and reports violations but never blocks them. Use it to test a policy before enforcing it.

Can I send both CSP and CSP-Report-Only?

Yes. Enforce the policy you trust; put the next, stricter draft in Report-Only so you see what would break.

Does Report-Only stop XSS?

No. Only an enforcing Content-Security-Policy header blocks. Report-Only is for measurement.

report-uri or report-to?

report-uri is still the most practical. report-to (Reporting API) is newer; many sites send both during a transition.

Why do I see violations only in the console?

You did not set report-uri / report-to, or your report endpoint is failing. Report-Only always logs to DevTools when the policy is present.

Published .

Try it

Related guides

Newest guides