How to Set Up a Content Security Policy
A practical, step-by-step guide to setting up a Content Security Policy — how to build a policy without breaking your site, and how to roll it out safely.
Content-Security-Policy is the highest-leverage security header available for reducing the impact of cross-site scripting, and it's also the one most likely to break something if configured carelessly. Setting it up well means understanding both halves of that — the protection it provides, and the care it requires.
What CSP actually does
CSP tells the browser which sources of scripts, styles, images, fonts and other resources are allowed to load and execute on your page. If an attacker manages to inject a script tag into your page through some other vulnerability, a well-configured CSP can prevent that injected script from actually running, because it doesn't come from an allowed source.
Step 1: Inventory what your site actually loads
Before writing a policy, know what you're allowing for. List:
- Where your own scripts and styles are served from
- Any third-party scripts (analytics, payment providers, embedded widgets, fonts)
- Any inline scripts or styles currently used directly in HTML
- Any dynamically loaded resources
This inventory is the difference between a policy that reflects reality and one that breaks functionality the moment it's enforced.
Step 2: Start with a restrictive default
A reasonable starting policy denies everything by default, then explicitly allows what's needed:
Content-Security-Policy: default-src 'self';
This alone blocks anything not served from your own origin — a strong baseline, but almost certainly too restrictive to enforce immediately without adjustment.
Step 3: Add directives for what you actually need
Build out from the default, directive by directive:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://trusted-analytics.example.com;
style-src 'self' https://fonts.googleapis.com;
font-src https://fonts.gstatic.com;
img-src 'self' data:;
frame-ancestors 'none';
Each directive controls a specific resource type, allowing more granular control than a single blanket policy.
Step 4: Handle inline scripts deliberately
Inline scripts (<script> tags with code directly in the HTML, rather than an external file) are blocked by a strict CSP by default — for good reason, since inline scripts are exactly what a successful XSS injection would produce. Options:
- Move inline scripts to external files — the cleanest, most secure fix
- Use nonces — a unique, per-request token added to both the CSP header and the specific inline script tags you intentionally trust
- Use hashes — a hash of the exact inline script content, added to the policy, so only that exact script is allowed to execute
Avoid 'unsafe-inline' if at all possible — it effectively disables CSP's protection against the exact attack class it exists to prevent.
Step 5: Deploy in report-only mode first
Before enforcing the policy, deploy it using Content-Security-Policy-Report-Only instead of the enforcing header. This logs what the policy would have blocked, without actually blocking anything — giving you visibility into what you missed in your inventory before it breaks real functionality for real users.
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report-endpoint;
Step 6: Review reports and adjust
Collect and review the violation reports generated during the report-only period. Each one represents either something legitimate you missed in your inventory, or — occasionally — an actual injection attempt already happening on your site, which is valuable information in its own right.
Step 7: Move to enforcement
Once the report-only period shows the policy isn't blocking legitimate functionality, switch to the enforcing Content-Security-Policy header. Keep reporting enabled even after enforcement, since new features or third-party scripts added later can trigger new violations you'll want visibility into.
Common mistakes
- Using
'unsafe-inline'or'unsafe-eval'broadly, which significantly weakens the protection CSP is meant to provide - Allowing overly broad sources, like
script-src *, which is present but provides minimal real protection - Skipping the report-only phase and deploying directly to enforcement, risking breaking legitimate functionality in production
- Setting a policy once and never revisiting it as the site's third-party integrations change over time
How does Averto fit in?
Averto's scanning evaluates CSP configuration quality specifically — flagging overly permissive policies, unsafe directives, and gaps between what's configured and what the site actually loads, rather than just checking that the header exists.
Frequently asked questions
Will setting up a CSP break my website?
It can, if deployed without first inventorying what the site loads and testing in report-only mode — which is why that sequence matters.
What's the difference between CSP and CSP-Report-Only?
The enforcing header actually blocks disallowed resources; the report-only header only logs what would have been blocked, without blocking anything — useful for safe testing before enforcement.
Should I use 'unsafe-inline' if I have a lot of inline scripts?
Avoid it where possible — it significantly weakens CSP's core protection. Moving to external scripts, or using nonces or hashes, preserves the protection while still allowing necessary inline code.
How often should a CSP be reviewed?
Whenever new third-party scripts or resources are added to the site, and periodically regardless, since policies can drift out of sync with what the site actually loads over time.