DevelopersJetpack 101

How to Force Content Security Policy (CSP) Headers on WordPress

man wearing headphones working on a laptop

Content Security Policy (CSP) is a browser feature that blocks unsafe content. It tells the browser what resources it can load, which can prevent attacks like cross‑site scripting (XSS). It also helps stop mixed content issues, which occur when secure pages load insecure files.

While CSP sounds complicated, you don’t need advanced technical experience to use it — just a simple starting policy, a safe way to test, and a little fine-tuning. This guide explains how to plan a policy, test it safely, enforce it, and maintain it over time. It includes a beginner path using a plugin and a developer path using server or code-level headers.

Who this guide is for and how to use it

This guide supports two experience levels:

  • The beginner track: You have WordPress admin access but don’t want to edit server config. You’ll use a plugin to add CSP and follow a clear test loop.
  • The developer track: You can edit Apache, Nginx, CDN headers, or WordPress code. You want direct control, versioning, and support for nonces or route-based policies.

You can read the shared foundation first, then jump to your path.

CSP basics before you get started

To deploy a solid policy, you need to understand a few terms:

  • Directive: This is a rule for one resource type. Example: script-src controls JavaScript.
  • Source expression: This is a value inside a directive. Example: ‘self’ or https://cdn.example.com.
  • ‘self’. This allows resources from your own domain and subresources served from it.
  • Report-only mode: The browser records violations but still loads blocked resources.
  • Enforcement mode: The browser blocks violations instead of only logging them.
  • Nonce: A random token that allowlists one inline script or style for one request.
  • Hash: An SHA-based fingerprint that allowlists one exact inline script or style text.

A CSP header is a semicolon separated list of directives. Browsers apply the rules to every page response that includes the header.

Before you set up CSP, make sure that:

  • You can clear all caches. This includes caching through plugins, your host, or a CDN.
  • You can test on staging or during a low traffic window. CSP can break your site layout or code if done incorrectly.
  • You know which third-party services your site uses. This includes things like analytics, fonts, ads, embeds, payment gateways, and chat widgets.

Decide on policy rules

When choosing which resources to allow, consider:

  • Scripts (script-src): From things like plugins, analytics, and inline code
  • Styles (style-src): Like theme CSS and external fonts
  • Images (img-src): From your Media Library and external image hosts
  • Connections (connect-src): From the REST API and external APIs
  • Other resources: Like fonts, frames, etc.

Start with a simple policy. For example:

default‑src 'self';
script‑src 'self' https://www.google‑analytics.com;
style‑src 'self' 'unsafe‑inline';
img‑src 'self' data:;

This allows files from your site, Google Analytics scripts, inline styles, and inline images. You can expand later.

At this point, it’s time to choose your track. If you’re a beginner, read on to the next line. If you’re more experienced, skip down to the developer track.

Beginner track: Add CSP with a plugin

Step 1: Install and configure a CSP plugin

We’ll use the Headers Security Advanced & HSTS WP plugin here, but any plugin that allows you to set custom response headers works.

  1. Install the plugin from the WordPress plugin directory.
  2. Go to Settings → Headers Security Advanced & HSTS WP.
  3. Find the CSP Header Contents section and paste your policy. Use the “starter policy template” section below if you need help writing a policy.
  4. Add a CSP Report URI if you’re in report-only mode (recommended). You can use:
    • A service such as Report URI or Sentry CSP reporting.
    • Your own endpoint if you already log security reports.
  5. Save your settings.
  6. Clear all caches.
CSP header content and report URI settings

Starter policy templates

Pick one that matches your setup, then refine it.

Standard template:

• default-src 'self';
• script-src 'self';
• style-src 'self' 'unsafe-inline';
• img-src 'self' data:;
• font-src 'self';
• connect-src 'self';

Typical WordPress setup with Google Fonts and Google Analytics:

• default-src 'self';
• script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com;
• style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
• font-src 'self' https://fonts.gstatic.com data:;
• img-src 'self' data:;
• connect-src 'self';

WordPress with YouTube embeds:

• default-src 'self';
• script-src 'self';
• style-src 'self' 'unsafe-inline';
• img-src 'self' data: https://i.ytimg.com;
• frame-src https://www.youtube.com https://www.youtube-nocookie.com;

Step 2: Check reports

After enabling report-only mode, browse your site and watch for reports. You can do this with tools like Chrome DevTools, keeping an eye out for CSP warnings.

A typical report contains keys such as:

  • blocked-uri: The resource the browser would block in enforcement mode.
  • violated-directive: The rule that would block it.
  • source-file: The page where the violation happened.

Your job is simple:

  1. Decide if the blocked resource is necessary and trusted.
  2. If it is, add its domain to the matching directive.
  3. Save, clear your cache, and reload.

Repeat until your critical pages generate no important violations.

Step 3: Move to enforcement mode

Once reports are clean, switch to enforcement mode.

  1. Replace the report-only header with an enforced CSP header.
  2. Paste the exact same policy.
  3. Save and clear caches.
  4. Reload key pages and confirm that no critical resources are blocked.

You’ll still see violations, but now the browser will block what breaks the policy.

Security

We guard your site. You run your business.

Jetpack Security provides easy‑to‑use, comprehensive WordPress site security, including real‑time backups, a web application firewall, malware scanning, and spam protection.

Secure your site

Developer track method 1: Add CSP at the server level

Are you a developer who wants more control? There are two primary ways to add a CSP to WordPress.

Apache, using .htaccess 

In .htaccess add the following, customizing as needed:

<IfModule mod_headers.c>
  Header set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
</IfModule>

After testing, switch to enforcement:

<IfModule mod_headers.c>
  Header set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
</IfModule>

Nginx

In your server block add the following, customized for your situation:

add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always;

Then enforce:

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always;

The always keyword makes sure headers are sent even on error responses.

CDN or reverse proxy

If your CSP should apply across multiple origins or you want a single control point:

  • Add the CSP header in your CDN response header rules.
  • Keep the policy string in version control if your CDN supports config as code.
  • Avoid setting CSP both at origin and CDN unless you intend to replace the origin value.

Developer track method 2: Add CSP via WordPress code

You may want CSP inside WordPress when your setup requires dynamic behavior. For example:

  • You need per-page policies, such as a tighter rule on admin-facing pages.
  • You plan to add nonces for inline scripts you control.
  • You want CSP rollout tied to theme or plugin deployment.

Example using wp_headers:

<IfModule mod_headers.c>
  Header set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
</IfModule>

After testing, change the header key to Content-Security-Policy.

Advanced CSP in WordPress: Nonces and hashes

Inline scripts are common in WordPress, as page builders, themes, and plugins may inject them. There are three ways to handle them:

  • Allow inline scripts globally with ‘unsafe-inline’. This is the easiest method, but isn’t as strong as the others.
  • Add hashes for specific inline blocks. This is stable when the content doesn’t change often.
  • Add nonces per request. This method is strong and flexible, but you must inject the nonce into every inline block you want to allow.

Suppose your theme outputs this inline script:

<script>
  console.log("hi");
</script>

You could compute a SHA 256 hash of the script content and add it to script-src:

script-src 'self' 'sha256-BASE64HASHVALUE';

Here’s a nonces example with WordPress:

add_action('send_headers', function() {
  $nonce = base64_encode(random_bytes(16));
  $policy = "default-src 'self'; script-src 'self' 'nonce-$nonce'; style-src 'self' 'unsafe-inline';";
  header("Content-Security-Policy: $policy");
  add_filter('script_loader_tag', function($tag, $handle) use ($nonce) {
    return str_replace('<script ', '<script nonce="' . esc_attr($nonce) . '" ', $tag);
  }, 10, 2);
});

This example adds the nonce to enqueued scripts. You still need to add the same nonce attribute to any inline scripts you output in templates.

Warning: Nonces in a mixed plugin environment take effort. If most of your inline scripts come from third-party plugins, hashes or carefully scoped ‘unsafe-inline’ may be the more practical choice.

Common WordPress CSP issues and fixes

Here are a few elements that may cause issues, and how to adjust for each one:

Page builders and themes with inline CSS

  • Problem: Layout looks unstyled.
  • Fix: Keep ‘unsafe-inline’ in style-src, or move to hashes if you control the inline blocks.

Third-party analytics and tag managers:

  • Problem: Console shows blocked scripts from Google or other tools.
  • Fix: Add their domains to script-src.

Payment and checkout scripts:

  • Problem: Checkout buttons fail or embedded payment frames don’t load.
  • Fix: Add payment domains to script-src, connect-src, and frame-src as needed.

Embedded media or social posts:

  • Problem: There are empty embed containers.
  • Fix: Add provider domains to frame-src or img-src.

A quick overview

Here’s a quick summary of the steps to put a CSP into place and maintain it over time.

  1. Write a small policy first.
    • Start with default-src ‘self’.
    • Add only the directives you know you need, usually script-src, style-src, img-src, font-src, and connect-src.
    • Keep the allowlist short. Every domain should have a purpose.
  2. Run in report-only mode.
    • Set Content-Security-Policy-Report-Only.
    • Browse multiple pages, not only the homepage. Include posts, archives, forms, and checkout pages.
    • Collect reports in DevTools or a reporting endpoint.
  3. Triage violations.
    • Read violated-directive first. That tells you what to fix.
    • Check blocked-uri. Decide if the resource is trusted and needed.
    • If yes, add the source to the right directive. If no, leave it blocked.
    • Change one thing at a time, then retest.
  4. Enforce once reports are clear.
    • Switch to Content-Security-Policy with the same policy.
    • Clear caches, reload key pages, and confirm the site works.
    • Watch the console for any last surprises.
  5. Tighten carefully.
    • If you control inline code, move from ‘unsafe-inline’ to hashes or nonces.
    • If third-party plugins inject inline scripts you cannot change, accept the targeted risk and document it.
  6. Maintain the policy.
    • After adding plugins or new features, adapt for new sources.
    • Review the allowlist every few months and remove dead entries.

This setup takes time up front. But once it’s running, you’ll block a lot of common attacks and reduce mixed‑content issues. It will give your WordPress site a stronger security baseline.

For even stronger protection, add Jetpack Security

Setting up a Content Security Policy is a smart move, adding a solid layer of defense to your site. But it shouldn’t be your only one. Attacks can still happen in other ways, like through outdated plugins, file changes, or comment spam.

That’s where Jetpack Security comes in. It gives your site extra tools that ultimately build a comprehensive security barrier, including:

  • Jetpack VaultPress Backup: Backs up your site automatically, in real time If something breaks, you can restore it in minutes.
  • Jetpack Scan: Monitors your site for malware or file changes. If it finds something, you get an alert.
  • Akismet: Filters spam from comments and forms, so you don’t have to waste time with manual removal.

These tools run quietly in the background, and you don’t need to be a security expert to use them. They also come together in one bundle designed specifically for WordPress.

CSP protects what the browser is allowed to load, while Jetpack Security watches everything else — from server-level changes to spam and backups. Together, they give your site stronger, more complete protection.

Learn more about Jetpack Security

This entry was posted in Developers, Jetpack 101. Bookmark the permalink.
Developers Jetpack 101

Jen Swisher profile

Jen Swisher

Jen is a Customer Experience Specialist for Jetpack. She has been working with WordPress and Jetpack for over a decade. Before starting at Automattic, Jen helped small businesses, local non-profits, and Fortune 50 companies create engaging web experiences for their customers. She is passionate about teaching others how to create on the web without fear.

Security

We guard your site. You run your business.

Jetpack Security provides easy‑to‑use, comprehensive WordPress site security, including real‑time backups, a web application firewall, malware scanning, and spam protection.

Secure your site

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.