Because nothing visibly breaks when you skip it. A site loads, converts, and passes every performance audit with no policy at all. Content Security Policy only pays off on the day something goes wrong, which makes it easy to push down the list forever. Most teams never get back to it.
The numbers back that up. According to the 2025 Web Almanac from HTTP Archive, "The adoption of CSP continued increasing from 18.5% last year to 21.9% this year, an increase of close to 20%." Growth is real, but roughly four in five sites still have no policy at all.
We think that is a mistake for most business sites, and a serious one for anything handling logins or payments. Here is what a Content Security Policy does, what it does not do, and how we roll one out without taking a site down in the process.
A Content Security Policy is a set of rules your server sends to the browser telling it what the page is allowed to load and run. MDN describes it as "a series of instructions from a website to a browser, which instruct the browser to place restrictions on the things that the code comprising the site is allowed to do."
The practical effect is a whitelist. Instead of a browser trusting any script that happens to appear in your HTML, it checks each one against your policy first. Scripts from an approved origin run. Scripts from anywhere else get blocked before they execute.
That inversion is the whole idea. Every other layer of web security tries to stop bad code from getting onto the page. Content Security Policy assumes something eventually will, and limits the damage it can do once it is there. It is a seatbelt, not a lock on the door.
Mainly cross-site scripting, with clickjacking as a strong secondary benefit. MDN is clear on the priority: "The primary use case for CSP is to control which resources, in particular JavaScript resources, a document is allowed to load. This is mainly used as a defense against cross-site scripting (XSS) attacks."
Cross-site scripting is the case that keeps us up at night on client sites, because it does not require breaching your server. An attacker only needs to get a script onto a page a user trusts. A vulnerable comment field, a compromised third-party widget, or a hijacked analytics script all get there.
Clickjacking is handled by a separate directive. MDN explains that "The frame-ancestors directive can be used to control which documents, if any, are allowed to embed this document in a nested browsing context such as an iframe. This is an effective protection against clickjacking attacks." Setting frame-ancestors to none blocks framing entirely.
What a CSP does not do is worth stating plainly. It will not patch a vulnerable dependency, stop a database leak, or protect a weak admin password. It is one layer among several, and our broader website security guide covers the rest of the stack it sits inside.
Send it as an HTTP response header. MDN states that "A CSP should be delivered to the browser in the Content-Security-Policy response header." Where you set that header depends on your host. On Vercel, for example, the vercel.json configuration file has a headers property whose documented purpose is to "Add custom HTTP headers to responses."
A minimal policy looks like default-src 'self'. That single instruction tells the browser to load scripts, styles, images, and fonts only from your own origin, and to block everything else. It is aggressive, and on most real sites it will break something immediately, which is exactly why you do not deploy it enforced on day one.
The header approach is the one we always reach for, because it applies to every response including ones the page did not generate. It is also the only way to use some features, which matters more than it sounds once you start rolling out safely.
Use the meta element instead, with your eyes open about the limits. MDN notes you can specify a policy "using the http-equiv attribute of your document's meta element, and this is a useful option for some use cases, such as a client-side-rendered single page app which has only static resources." It then warns that "this option does not support all CSP features."
The most important gap is reporting. MDN states that "unlike a normal content security policy, a report-only policy cannot be delivered in a meta element." That removes the safest deployment path, which we will come to shortly, so a meta tag policy has to be written more conservatively from the start.
If your platform gives you a code injection field but no header control, a meta tag policy is still better than nothing. Just accept that you are trading away the safety net, and test far more carefully before you ship it.
Because it tells the browser to trust any inline script on the page, which is precisely what a cross-site scripting attack produces. Adding it is the fastest way to make a broken site work again, and it is also the fastest way to make your policy decorative rather than protective.
This is not a rare mistake. The 2025 Web Almanac found that "An overwhelming 92% of websites use the unsafe-inline attribute on both desktop and mobile." So the majority of sites that have gone to the trouble of writing a policy have then disabled its main protection.
The proper alternative is a nonce or a hash. MDN documents both: a nonce takes the form script-src 'nonce-{RANDOM}' and must be regenerated per request, while a hash takes the form script-src 'sha256-{HASHED_SCRIPT}' and pins one exact script. Nonces suit server-rendered pages. Hashes suit static builds where the inline script never changes.
The reason so many teams give up here is third-party code. Tag managers, chat widgets, and analytics snippets all want to inject script at runtime, and each one adds an exception to your policy. That cost is real, and it is one more argument for keeping the third-party layer small. We wrote about that tradeoff in our piece on third-party scripts and performance.
Start in report-only mode and let the browser tell you what would have broken. MDN describes exactly this path: "To ease deployment, CSP can be deployed in report-only mode. The policy is not enforced, but any violations are sent to the reporting endpoint specified in the policy."
You send the same policy under the Content-Security-Policy-Report-Only header instead of the enforcing one. Nothing is blocked. Every violation that would have occurred gets posted to your endpoint. After a week of real traffic you have a list of every script, font, and frame your site actually depends on, including the ones nobody remembered adding.
Pair it with the reporting directives. MDN recommends report-to alongside the Reporting-Endpoints header, and notes that the older report-uri directive is deprecated but that "you should declare both until report-to is supported in all browsers." Declaring both is the pragmatic call today.
You can also run both headers at once. MDN confirms that when an enforcing header and a report-only header are both present, "both policies are honored", so you can enforce a policy you trust while testing a stricter one behind it. That is how we tighten a policy over time without a scary all-or-nothing switch.
Almost not at all. The policy is a response header, so it adds a few hundred bytes per request and no rendering work. There is no crawling penalty, no ranking signal, and no interaction with how search engines read your page. Googlebot is not blocked by a policy that permits your own scripts.
The one real risk is self inflicted. If your policy blocks a script your own page needs, the page can break for users and for rendering crawlers alike. That is a bug in your policy, not a property of Content Security Policy itself, and report-only mode exists specifically to catch it before it ships.
It is worth putting CSP in context with the other security headers, which are cheaper still. The same 2025 Web Almanac reports HSTS "up to 36% of all pages" and notes that the share of requests sent over HTTPS reached "over 98.8% for mobile connections". HTTPS is effectively universal now. The headers that build on it are where the remaining ground is.
Every site that accepts user input, handles payments, or has a login should. For a purely static brochure site the case is weaker, but it is still worth adding, because the cost is one header and the failure mode you are insuring against is somebody else's script running on your domain.
Our honest position is that CSP is undervalued relative to how cheap it is. Teams will spend a week shaving 200 milliseconds off a page load and never spend an afternoon on a security header. Both matter, and only one of them can end with a customer's card details somewhere they should not be.
Where we do push back is on rushing it. A badly tuned policy that blocks your own checkout script is worse than no policy at all. If you are working inside a platform where custom code lands through an injection field, our notes on custom code in Webflow cover how to keep that layer tidy enough to write a policy against.
Check what you have first. Open your browser developer tools, load your homepage, and look at the response headers for Content-Security-Policy. If it is missing, add a report-only policy of default-src 'self' with a reporting endpoint, then leave it running for a week and read what comes back.
That single week of reports is the most useful security artifact most sites will ever produce. It is a complete inventory of every external thing your site loads, which is almost always longer than anyone expected. Fixing what that list reveals often matters more than the policy you eventually enforce.
We do this on builds where security actually matters, and we are careful about it because we would rather ship no policy than a broken one. If you want a second pair of eyes on your headers, or you are not sure whether your setup can support them, we are happy to walk through it with you. You can find us at phoenix.studio.
Tell us where you want to go. We'll tell you how we'd get you there.