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
| Header | Blocks? | Reports? | Use for |
|---|---|---|---|
Content-Security-Policy | Yes | Yes (if report-uri / report-to set) | The live policy visitors must obey |
Content-Security-Policy-Report-Only | No | Yes | Trying 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
- 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. - Report-Only first. Ship only
Content-Security-Policy-Report-Onlyfor 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). - Enforce the known-good policy. Flip the same string to
Content-Security-Policy. - 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
report-uri /csp-report;— classic. The browser POSTs a JSON report to that path on your origin (or an absolute URL). Widely supported; marked deprecated in favour of Reporting API but still the practical choice for most sites.report-to csp-endpoint;plus a separateReporting-Endpoints: csp-endpoint="https://…"(or the olderReport-Toheader) — newer Reporting API. Support is uneven; many teams keepreport-urias a fallback.- Console only — with no report destination, Report-Only still prints every violation in DevTools. Fine for local testing; useless in production if you never look.
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
- It never blocks. If you only ship Report-Only, XSS still runs; you are measuring, not protecting.
upgrade-insecure-requestsandblock-all-mixed-contentonly take effect on an enforcing policy.- Several enforcing CSP headers are AND-ed (a request must pass every one). Report-Only headers report independently and do not change that.
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 .