Understanding Content Security Policy Headers for Small Site Owners
The first time I added a Content Security Policy header to a real site, I broke the layout, the analytics tracking, and the embedded contact form all at once, within about five minutes. That is also, somewhat backward, why it is worth learning properly.
A Content Security Policy decides, script by script, what your site is allowed to run at all.
In this post
- What a CSP actually controls
- The specific attack it is built to stop
- Reading a real CSP header, piece by piece
- Why it breaks things, and how to roll it out without chaos
- A realistic starting point for a small site
Content Security Policy, almost always shortened to CSP, is one of the more powerful browser security features available to site owners, and one of the least understood outside professional web development. It is delivered as an HTTP header, a small instruction sent alongside every page load, and it fundamentally changes what a browser is willing to let your site's pages do.
What a CSP actually controls
A CSP is a set of rules, sent by your server, telling the visitor's browser exactly which sources of scripts, styles, images, and other content are allowed to load and execute on your page. Anything not explicitly permitted gets blocked by the browser itself, regardless of how it ended up on the page, whether through your own code, a compromised plugin, or an attacker who managed to inject something malicious. It is a safety net that assumes something might go wrong elsewhere and limits the damage if it does.
The specific attack it is built to stop
CSP exists primarily to blunt cross-site scripting attacks, commonly called XSS, where an attacker manages to get malicious JavaScript to run on your page, often through an unsanitized comment field, a vulnerable plugin, or a compromised third-party script. Without a CSP, if that malicious script makes it onto the page, it runs with full access to everything your own legitimate scripts can do, reading cookies, capturing form input, or redirecting visitors elsewhere entirely. With a properly configured CSP, even if the malicious script makes it onto the page, the browser refuses to execute it because it did not come from an approved source, stopping the attack at the browser level rather than relying entirely on the vulnerability never happening in the first place.
Reading a real CSP header, piece by piece
A basic CSP header might look like this:
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
default-src 'self'sets the baseline rule, only allow content from your own domain, unless a more specific directive overrides itscript-src 'self' https://www.googletagmanager.compermits scripts from your own domain plus one explicitly named third party, and blocks scripts from anywhere else, including inline scripts unless separately allowedstyle-src 'self' 'unsafe-inline'allows your own stylesheets plus inline styles, a common compromise since many CMS themes rely on inline styling that would otherwise breakimg-src 'self' data: https:allows images from your own domain, inline data images, and any HTTPS source, a looser rule often necessary for sites with varied embedded content
Why it breaks things, and how to roll it out without chaos
A CSP is strict by design, which is exactly why adding one to an existing site often breaks something immediately, an analytics script from a domain you forgot you were using, a font loaded from a CDN, an embedded video player, a plugin injecting inline JavaScript. This is normal, expected, and not a sign you are doing it wrong.
The standard, much safer rollout method is to start in report-only mode:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
In this mode, the browser does not actually block anything, it only logs what would have been blocked under your proposed policy, letting you see exactly what needs to be added to your allowlist before you switch to actual enforcement. Only move from report-only to a real, enforced CSP once you have reviewed those reports and adjusted your policy to account for everything your site legitimately needs.
A realistic starting point for a small site
- Deploy a report-only CSP first, with a reasonably strict starting policy, rather than guessing your final rules upfront
- Let it run for at least a week or two of normal traffic, so you catch content that only loads occasionally, like an embedded form on one specific page
- Review the reports and build your actual allowlist based on what your site genuinely uses, not a generic template copied from elsewhere
- Switch to an enforced policy once the report-only version shows a clean pattern with no unexpected blocks
- Recheck your policy any time you add a new third-party script, widget, or embed to your site, since it needs to be explicitly allowed or it will simply stop working with no obvious error message pointing at the cause
Being honest about the effort involved: a properly tuned CSP takes real, deliberate work to set up correctly on anything beyond a very simple static site, and it is one of the more commonly skipped security headers for exactly that reason. It is also one of the more effective ones available, precisely because it adds a browser-level backstop against a category of attack that input sanitization alone cannot fully guarantee it will always catch.
A well-configured CSP will not make your site invulnerable to everything, but it closes off a meaningful category of damage even when something else on the site goes wrong. The MDN documentation on Content Security Policy is the most complete technical reference if you want to go deeper into the full set of available directives once you have the basic idea down.