WordPress and Content Security Policy have a complicated relationship. WordPress was built in an era before CSP existed, and it shows. The admin panel injects inline scripts constantly. Plugins do whatever they want. Themes include jQuery from who-knows-where.
But here’s the thing: your WordPress site is a target. It powers 43% of the web, which means attackers have spent years finding ways to exploit it. Adding CSP is one of the most impactful things you can do to protect it.
Let’s walk through how to actually make this work.
The Core Problem
WordPress has two distinct parts that need different policies:
- The frontend — What visitors see. This is where CSP is most valuable and easiest to implement.
- The admin panel — Where you manage content. This is a CSP nightmare.
The admin panel uses inline scripts extensively. The Block Editor (Gutenberg) generates inline JavaScript. The Customizer, media uploader, and various admin screens all inject scripts dynamically. Trying to apply a strict CSP to wp-admin is an exercise in frustration.
So here’s my recommendation: use a strict policy on the frontend and a permissive policy on the admin. It’s pragmatic and it works.
The Frontend Policy
Add this to your theme’s functions.php or a custom plugin:
function my_csp_headers() {
// Skip admin — we'll handle that separately
if (is_admin() || is_user_logged_in()) {
return;
}
header("Content-Security-Policy: "
. "default-src 'self'; "
. "script-src 'self' https://www.googletagmanager.com; "
. "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
. "img-src 'self' data: https: https://i0.wp.com https://i1.wp.com https://i2.wp.com https://s.w.org; "
. "font-src 'self' https://fonts.gstatic.com; "
. "connect-src 'self' https://www.google-analytics.com; "
. "frame-ancestors 'none'; "
. "base-uri 'self'; "
. "form-action 'self'");
}
add_action('send_headers', 'my_csp_headers');
Let me break down what’s going on here:
The is_admin() check — This ensures the policy only applies to frontend pages. Your admin panel won’t be affected.
img-src includes WordPress CDN domains — i0.wp.com, i1.wp.com, i2.wp.com are WordPress.com’s CDN, used for Jetpack Photon and gravatar images. If you use Jetpack or any plugin that serves images through WordPress.com’s infrastructure, you need these. s.w.org is for WordPress emoji images.
style-src ‘unsafe-inline’ — Yes, this is a compromise. Many WordPress themes and page builders inject inline styles. Gutenberg itself adds inline styles for block formatting. You could fight this, but you’d be fighting your entire ecosystem. Accept the compromise and move on.
The emoji problem — WordPress injects emoji JavaScript by default. If you don’t need emojis (you probably don’t on a professional site), disable them entirely:
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
This removes the emoji script injection and saves you a CSP headache.
The Admin Policy
For the admin panel, be permissive but still get some value:
function my_admin_csp_headers() {
if (!is_admin()) {
return;
}
header("Content-Security-Policy: "
. "default-src 'self'; "
. "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
. "style-src 'self' 'unsafe-inline'; "
. "img-src 'self' data: https:; "
. "font-src 'self'; "
. "connect-src 'self'; "
. "frame-src 'self'");
}
add_action('send_headers', 'my_admin_csp_headers');
This is weak from a script perspective, but you still get frame protection and connect restrictions. It’s not perfect, but it’s better than nothing.
Handling Common WordPress Plugins
Google Analytics
Most people use a plugin for this. The policy needs:
Contact Form 7
If you use Contact Form 7 with reCAPTCHA:
WooCommerce
WooCommerce adds Stripe, PayPal, and various other payment scripts:
Yoast SEO
Yoast itself doesn’t add external scripts, but if you enable their tracking:
WP Rocket / Caching Plugins
These can interfere with CSP headers by caching pages with embedded nonces. If you use nonce-based CSP, exclude the CSP header from caching or use a cache-busting mechanism.
The Plugin Route
If editing PHP isn’t your thing, there are plugins that handle CSP:
- HTTP Headers — Simple, lets you add any header including CSP
- Security Headers — More comprehensive, includes other security headers too
The advantage of plugins is that they sometimes handle the admin/frontend split automatically. The disadvantage is less control over the exact policy.
Testing Checklist
- Start with report-only mode (change
Content-Security-PolicytoContent-Security-Policy-Report-Only) - Browse your entire site — every page, every post, every category
- Check the browser console for violations
- Fill out and submit every form
- Test any interactive features (search, filters, infinite scroll)
- Check on mobile — some plugins load different scripts on mobile
- If everything looks good after a week, switch to enforcement