← All guides

Content Security Policy Report-Only Rollout

CSP is the header most likely to break your site if you guess. Report-only mode means you never have to guess.

Do this now

Ship a candidate policy under the Content-Security-Policy-Report-Only header. Browsers evaluate it against every page load and report violations — but block nothing:

Content-Security-Policy-Report-Only: default-src 'self';
  script-src 'self'; style-src 'self'; img-src 'self' data:;
  object-src 'none'; base-uri 'self'; frame-ancestors 'none';
  report-uri https://yourdomain.report-uri.com/r/d/csp/reportOnly

(One line in your server config; it's wrapped here for readability.) Don't hand-write the policy — our security headers generator builds a starting CSP for your stack and gives you the exact nginx, Apache or Cloudflare syntax to paste.

Why report-only first

An enforcing CSP that's wrong doesn't degrade gracefully — it blocks your payment provider's script, your web fonts, your analytics, sometimes your own stylesheets, all at once, for every visitor. Almost every "CSP broke production" story starts with someone deploying an enforcing policy that was written from memory of what the site loads rather than evidence.

Report-only inverts the risk. The browser runs the full policy evaluation on real traffic — every browser, every page, every third-party tag your marketing team added last quarter — and tells you what would have been blocked. Users notice nothing. You get an inventory of everything your policy missed, generated by production itself. The header can only be set server-side, by the way — Content-Security-Policy-Report-Only is ignored in a <meta> tag, so this is a server-config change, not a template change.

Step 1 — Draft a candidate policy

Start strict and let the reports tell you what to loosen — the reverse (start loose, tighten later) never happens in practice. A sane baseline: default-src 'self', explicit script-src and style-src, object-src 'none', base-uri 'self', and frame-ancestors to control framing. The generator outputs this baseline with your known third-party origins added, in copy-paste syntax for your server.

The blocker you'll hit immediately is inline code: <script> blocks and onclick= attributes fail a strict script-src 'self'. Resist the urge to paper over it with 'unsafe-inline' — that keyword gives back most of the XSS protection you came for. Move inline scripts into files where practical, and use nonces or hashes for the ones that have to stay.

Step 2 — Stand up a report endpoint

Violation reports have to go somewhere, and this corner of the platform is honestly messy. There are two mechanisms:

The pragmatic move: send both directives, and treat report-uri as the one doing the work. For the endpoint itself, a hosted service (report-uri.com has a free tier) or a few lines on your own server both work — reports arrive as JSON POSTs. Expect volume: a busy site can generate thousands of reports a day, most of them duplicates.

Step 3 — Deploy and triage for two to four weeks

With the report-only header live, violations stream in. Triage them into three buckets:

Give it real calendar time — two weeks minimum, a month if you have monthly billing or reporting flows that only run occasionally. A policy validated only against yesterday's traffic misses the code paths that run on the 1st.

Step 4 — Enforce, and keep a canary

When a full business cycle has produced no legitimate violations, rename the header: Content-Security-Policy-Report-Only becomes Content-Security-Policy, same value, keep the reporting directives. Violations are now blocked — and still reported, which is your production alarm for regressions and your evidence log if someone actually attempts XSS.

A pattern worth keeping afterwards: when you next want to tighten the policy, run the stricter candidate in a report-only header alongside the enforcing one. Browsers evaluate both independently, so you can trial every future change with zero risk, exactly like the first rollout.

Verify the header is actually out there

Config files lie — proxies strip headers, CDNs override them, a location block shadows the one you edited. After deploying, run your site through our security headers checker to see the headers your server really sends, and use the generator whenever you need the syntax for a policy change. If you're rolling out CSP for a client, the Vuln Assessment Report Toolkit is a ready-made template for writing up the before/after.