CSP for WordPress, Drupal, and Joomla: Working Examples
Table of Contents
🔧 Validate Your CSP Policy
Copy a CSP example, deploy it, then verify it works with a real scan.
Verify with headertest.com →
🔧 Validate Your CSP Policy
Copy a CSP example, deploy it, then verify it works with a real scan.
Verify with headertest.com →🔧 Validate Your CSP Policy
Copy a CSP example, deploy it, then verify it works with a real scan.
Verify with headertest.com →🔧 Validate Your CSP Policy
Copy a CSP example, deploy it, then verify it works with a real scan.
Verify with headertest.com →Every CMS platform fights CSP in its own way. WordPress injects inline scripts everywhere. Drupal has its own asset management layer. Joomla plugins do whatever they want with no regard for security headers.
Here are working CSP configurations for each, based on real deployments I’ve done. These won’t break your site — I’ve tested them.
WordPress#
WordPress is the hardest because of how many things inject inline scripts. The Gutenberg editor, plugins, themes, and WordPress core itself all add JavaScript dynamically. The admin panel is essentially impossible to fully lock down.
My approach: different policies for frontend and admin.
Frontend Only#
Add to functions.php or a custom plugin:
add_action('send_headers', function() {
if (is_admin()) 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'");
}, 1);
Let me explain some of the less obvious choices:
i0/i1/i2.wp.com — WordPress.com’s CDN, used by Jetpack Photon for image optimization and gravatar avatars. If you use Jetpack or any plugin that offloads images to WordPress.com infrastructure, you need these.
s.w.org — WordPress emoji images. Even if you don’t use emojis in your content, WordPress loads emoji JavaScript by default. The images it tries to load come from s.w.org.
‘unsafe-inline’ for style-src — I know, I know. But WordPress themes and page builders (Elementor, Divi, Gutenberg) inject inline styles constantly. Fighting this is not worth the effort for most sites. CSS can’t execute JavaScript, so the risk is minimal.
WordPress + WooCommerce#
WooCommerce adds payment processing scripts. If you use Stripe or PayPal:
add_action('send_headers', function() {
if (is_admin()) return;
header("Content-Security-Policy: "
. "default-src 'self'; "
. "script-src 'self' https://www.googletagmanager.com https://js.stripe.com https://www.paypal.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 https://www.paypalobjects.com; "
. "font-src 'self' https://fonts.gstatic.com; "
. "connect-src 'self' https://www.google-analytics.com https://api.stripe.com; "
. "frame-src 'self' https://js.stripe.com https://hooks.stripe.com; "
. "frame-ancestors 'none'; "
. "base-uri 'self'; "
. "form-action 'self' https://www.paypal.com");
}, 1);
Note the form-action includes PayPal — WooCommerce redirects to PayPal for checkout, and without it in form-action, the redirect will be blocked.
WordPress + Elementor#
Elementor injects a massive amount of inline CSS for every section, column, and widget. You absolutely need unsafe-inline for style-src. Don’t even try to fight it.
WordPress Admin Panel#
For wp-admin, be permissive. It’s only accessible to logged-in users anyway:
add_action('send_headers', function() {
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'; "
. "frame-src 'self'");
}, 1);
WordPress Quick Tips#
- Disable emojis to remove unnecessary scripts:
remove_action('wp_head', 'print_emoji_detection_script', 7); - Use a CSP plugin like “HTTP Headers” if you don’t want to edit PHP
- Test with all plugins active — some only load scripts on specific pages
- Disable XML-RPC if you don’t use it — it’s a security risk regardless of CSP
Drupal#
Drupal has better architecture for CSP, with an official module:
composer require drupal/csp
Configuration in config/sync/csp.settings.yml:
csp:
mode: enforce
report-only: false
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:"
font-src:
- "'self'"
- "https://fonts.gstatic.com"
connect-src:
- "'self'"
frame-ancestors:
- "'none'"
base-uri:
- "'self'"
form-action:
- "'self'"
The Drupal CSP module also supports nonces through the UI. Go to Configuration → Content Security Policy → Settings and enable “Use nonces for inline scripts.” This is the cleanest way to handle inline scripts in Drupal — the module generates nonces automatically for inline scripts added through Drupal’s asset system.
Drupal with Layout Builder#
Layout Builder can inject inline styles. If you use it, add 'unsafe-inline' to style-src.
Joomla#
Joomla doesn’t have a great CSP module ecosystem. The most reliable approach is .htaccess:
<IfModule mod_headers.c>
<FilesMatch "\.(php)$">
Header always set 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'; frame-ancestors 'none'; base-uri 'self'"
</FilesMatch>
</IfModule>
The unsafe-inline and unsafe-eval are unfortunately necessary for most Joomla installations. The admin panel and many extensions depend on them. It’s not ideal, but it’s the practical reality of Joomla’s architecture.
If you’re running a modern Joomla 4+ site with a minimal setup, you might be able to remove unsafe-eval. Test in report-only mode first.
General CMS Advice#
- Always separate frontend and admin policies — Don’t try to use the same strict policy for both. Your admin panel needs more flexibility.
- Use report-only first — Deploy and monitor for at least a week. CMS platforms have too many moving parts to get CSP right on the first try.
- Audit your plugins — Every active plugin might add scripts, styles, or iframes. Check each one.
- Accept compromises —
unsafe-inlinefor style-src on CMS platforms is normal and acceptable. Focus your strictness on script-src. - Remove unused plugins — Fewer plugins means fewer CSP violations and a faster site. Win-win.
- Test every page type — Homepage, blog posts, category pages, search results, 404 page, contact forms. Different pages load different resources.