A Content Security Policy tells the browser which resources a page is allowed to load. The reason most teams never ship one is the fear of breaking the site on a Friday afternoon. There is a way around that: send the policy in report-only mode first, read what it would have blocked, fix those things, and only then enforce. The header is Content-Security-Policy, its directives are separated by semicolons, and the report-only variant is a different header entirely.

The one directive to understand first

default-src is the fallback. MDN describes it as setting "a fallback policy for all resources whose directives are not explicitly listed", so a policy of default-src 'self' restricts everything to the page's own origin until a more specific directive says otherwise.

That makes the mental model simple. Start restrictive, then open exactly what you need:

PolicyEffect
default-src 'self'Every resource type must be same-origin
default-src 'self'; img-src 'self' example.comImages may also come from example.com; everything else stays same-origin
default-src 'self'; font-src 'self' fonts.gstatic.comWeb fonts may come from Google's font host; everything else stays same-origin

The directives you will almost certainly need to name explicitly are script-src, style-src, img-src, font-src and connect-src, because analytics, embedded fonts and API calls are the things a same-origin default breaks first.

Report-only is the whole trick

There are two headers, and the difference is the entire safety mechanism. MDN puts it plainly: the policy in Content-Security-Policy "is enforced while the Content-Security-Policy-Report-Only policy generates reports but is not enforced."

So you can ship a policy that would break your site, learn precisely how it would break it, and no visitor notices. One constraint is worth knowing before you plan the rollout: a report-only policy cannot be delivered in a <meta> element, so it has to come from the server as a real response header.

StepHeaderWhat you are doing
1Content-Security-Policy-Report-OnlyPublish your intended policy; nothing is blocked
2sameCollect violation reports for a week of real traffic
3sameAdd the hosts you actually depend on; remove the ones you do not
4sameWait until reports go quiet
5Content-Security-PolicySwitch the header name. The policy is unchanged

Step 4 is the one to be patient about. A week of traffic surfaces the marketing tag that only fires on the pricing page and the embed that only appears in one old blog post.

The two values that undo the work

'unsafe-inline' permits inline scripts, inline event handler attributes and javascript: URLs. MDN's warning is direct: developers should avoid it "because it defeats much of the purpose of having a CSP. Inline JavaScript is one of the most common XSS vectors, and one of the most basic goals of a CSP is to prevent its uncontrolled use."

'unsafe-eval' permits eval(), the Function() constructor and string arguments to setTimeout() and setInterval(), and carries the same advice.

Adding either one to make the reports stop is the most common way a CSP ends up decorative. If inline scripts are the blocker, the answer is a nonce or a hash, not 'unsafe-inline'.

A detail that saves an afternoon

When a directive contains a nonce or a hash expression, browsers ignore 'unsafe-inline' in that same directive. So a policy carrying both is not belt-and-braces; the nonce wins and the 'unsafe-inline' does nothing. Worth knowing before you spend an afternoon wondering why your inline script is still blocked. Note that 'unsafe-eval' is not disabled the same way.

Nonce or hash: pick by how your pages are built

Both let specific inline scripts run without opening the door to all of them. They suit different architectures.

NonceHash
How it worksServer puts a random value in the header and the same value on the <script> tag; the browser runs the script only if they matchServer publishes the SHA-256, 384 or 512 hash of the script contents, Base64-encoded; the browser hashes the script and compares
Hard requirementThe nonce "must be different for every HTTP response, and must not be predictable"The hash changes whenever the script text changes
Best fitServer-rendered pages that can generate a value per responseStatic pages and client-rendered sites, because both the policy and the content can stay fixed
External scriptsNonce on the tagAlso needs the integrity attribute

The nonce requirement is a real constraint, not a style preference. A nonce reused across responses, or generated from anything guessable, provides no protection at all. If your site is served from a static host or a CDN cache that cannot vary a header per request, hashes are the honest choice.

What to do this week

Pick your highest-traffic page and send Content-Security-Policy-Report-Only: default-src 'self' with a reporting endpoint. Do not try to write the perfect policy first. The report will tell you what your site actually loads, which is almost always a longer list than anyone expects, and that list is the real starting point.

Frequently asked questions

Can I add a CSP without touching the server?
An enforcing policy can be delivered in a <meta> element, but a report-only policy cannot. Since report-only is the safe way to start, plan on setting a real response header.

Will a CSP slow the site down?
It is a response header the browser evaluates as it loads resources, not an extra network round trip. The practical cost is engineering time spent finding what your pages load.

Is default-src 'self' enough on its own?
It is a sound starting point, and it is the fallback for directives you have not listed. Most real sites then need to name script-src, style-src, img-src, font-src or connect-src for the third-party hosts they genuinely use.

We added a nonce and inline scripts are still blocked. Why?
Check that the nonce on the tag matches the one in the header for that exact response. Also remember that a nonce in a directive causes 'unsafe-inline' in the same directive to be ignored, so removing the nonce is not the fix.

Sources

MDN, Content Security Policy guide. Quoted wording on report-only enforcement, the default-src fallback, the 'unsafe-inline' warning and the nonce uniqueness requirement is MDN's own.

More in this section Technology →