A Content Security Policy tells the browser exactly which sources are allowed to load scripts, styles, and other resources on your site. Done right, it stops a huge class of XSS attacks even if malicious code somehow gets injected — the browser simply refuses to execute anything from a source not on the list. Done carelessly, it breaks your page builder, your analytics, and your embedded fonts all at once.
Start in report-only mode
Never switch on an enforced CSP on a live site without testing first — you will break something:
add_action('send_headers', function() {
header("Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline' *.googletagmanager.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data: *.gravatar.com;");
});
Report-only mode logs violations to the browser console without actually blocking anything, so you can see what your policy would break before it does.
Reading the violations
Open DevTools console on a few key pages — homepage, a blog post, checkout if you run WooCommerce — and note every blocked-resource warning. Page builders in particular tend to inject inline styles and scripts constantly, which is why 'unsafe-inline' often has to stay in the policy longer than you’d like, at least for style-src.
Switching to enforced
header("Content-Security-Policy: default-src 'self'; script-src 'self' *.googletagmanager.com; ...");
Once the report-only version runs clean for a week or two across your actual traffic, not just your own testing, flip it to the enforced header.
The realistic goal
A perfect CSP with no unsafe-inline anywhere is genuinely hard on a WordPress site running a page builder — plugin authors don’t always follow best practices for their own inline scripts. A CSP that’s 80% strict and actually enabled protects you more than a theoretically perfect one that never makes it out of report-only mode because it kept breaking things.