WordPress Security

Content Security Policy for WordPress: A Practical, Step-by-Step Approach

Content Security Policy for WordPress: A Practical, Step-by-Step Approach

Of all the security headers you can add to a website, Content Security Policy is the most powerful and the most likely to break things. A well-built policy can stop injected scripts from running, prevent stolen data from being sent to an attacker’s server and block your pages from being framed by malicious sites. A careless one can break your editor, your analytics, your payment form and your embedded videos all at once.

On WordPress sites, building a CSP takes patience because WordPress, plugins and themes load scripts from many places, including inline scripts scattered through the page. But it’s achievable, and for stores and high-value sites, it’s increasingly worth the effort. Here’s the process I use.

What a Content Security Policy does

A CSP is an HTTP header that tells the browser which sources of content are allowed on your pages. If something isn’t allowed, the browser blocks it.

For example, a policy might say:

  • Scripts may only load from your own domain and your payment provider.
  • Images may load from your domain and your CDN.
  • Forms may only submit to your own domain.
  • No other site may display your pages in a frame.

If an attacker injects a script tag pointing to their server, the browser refuses to load it. If injected code tries to send card data to an unknown domain, the browser blocks the request. That’s why CSP is one of the strongest defences against cross-site scripting and web skimming.

Why it’s tricky on WordPress

A typical WordPress page includes:

  • Scripts and styles from the theme and plugins.
  • Inline scripts that WordPress and plugins use to pass settings to JavaScript.
  • Inline styles from the block editor and page builders.
  • Third-party scripts for analytics, chat, fonts, maps and videos.
  • Payment provider scripts and frames on checkout.

A strict policy that disallows inline scripts breaks many of these. So the realistic approach on most WordPress sites is to start permissive, observe, and tighten step by step.

The directives that matter most

A CSP is made of directives. The most important for WordPress sites:

  • default-src: the fallback for any type of content not covered by a more specific directive.
  • script-src: where scripts can load from, and whether inline scripts are allowed.
  • style-src: where stylesheets can load from.
  • img-src: where images can load from.
  • font-src: where fonts can load from.
  • connect-src: where scripts can send requests, such as AJAX, fetch and analytics. This is the directive that stops data being sent to an attacker’s server.
  • frame-src: which sites your pages can embed in frames, like videos and payment forms.
  • frame-ancestors: which sites may embed your pages. Setting this to ‘self’ protects against clickjacking.
  • form-action: where forms may submit.
  • object-src: plugins like Flash. Set this to ‘none’.
  • base-uri: restricts the base element, which attackers can abuse. Set to ‘self’.
  • upgrade-insecure-requests: tells browsers to load HTTP resources over HTTPS.

Step 1: Start with the easy, safe directives

Some directives rarely break anything and give real protection. Start with these:

On Nginx:

This prevents clickjacking, blocks old plugin content, stops base tag abuse and upgrades insecure requests. It’s a meaningful improvement with almost no risk.

Step 2: Build the full policy in report-only mode

Report-only mode lets you test a policy without enforcing it. The browser reports what it would have blocked, but blocks nothing. Use the Content-Security-Policy-Report-Only header:

This example is only a starting point. Your sources will differ.

Collecting violation reports

Violations appear in the browser’s developer console, which is enough for small sites: browse every page type and note what’s reported. For larger sites, add a report-uri or report-to directive pointing to a reporting endpoint or a CSP reporting service, which collects violations from real visitors.

Step 3: Inventory your sources

Browse every type of page, logged out and logged in:

  • Home, posts, pages and archives.
  • Forms and search.
  • Product pages, cart, checkout and My Account, for stores.
  • The block editor and admin screens, if the policy applies there.

Note every domain that appears in violation reports. For each, decide whether it’s legitimate and needed. You’ll often discover scripts you forgot about, like an old tracking pixel or a widget nobody uses. Remove those rather than allowing them.

Step 4: Decide about inline scripts

Inline scripts are the hardest part. ‘unsafe-inline’ in script-src allows all inline scripts, including injected ones, which weakens much of the protection against cross-site scripting.

Options, from easiest to strongest:

  1. Allow ‘unsafe-inline’ but tightly restrict external script sources and connect-src. You still block scripts loaded from unknown domains and data sent to unknown servers, which stops many skimming attacks.
  2. Use nonces. Generate a random value for each page load, add it to the CSP header and to every legitimate script tag. Only scripts with the correct nonce run. WordPress’s script functions, like wp_print_inline_script_tag, can add attributes to scripts, and some security plugins implement nonce-based CSP for WordPress. Third-party plugins that print raw script tags won’t have the nonce, so they need handling.
  3. Use hashes for specific inline scripts that never change.

For many WordPress sites, option one with a strict connect-src is a practical, meaningful policy. For checkout pages and high-value sites, aim for nonces.

Step 5: Apply different policies to different areas

You don’t need one policy for the whole site. A stricter policy on the checkout, where the risk is highest and the number of scripts should be smallest, gives strong protection exactly where it matters.

On Nginx, apply headers by location:

Check your payment provider’s documentation for the exact domains their scripts and frames need. Most publish CSP guidance.

The admin area is often best left with a lighter policy or only the safe directives, because the block editor and plugins use many inline scripts and dynamic features.

Step 6: Enforce and keep monitoring

When report-only violations have settled down to only things you’ve decided to block, switch from Content-Security-Policy-Report-Only to Content-Security-Policy. Keep a reporting endpoint active, so you notice when a plugin update or new marketing tag starts triggering violations.

Treat CSP changes like code changes: test on staging, document why each source is allowed and review the policy whenever you add or remove a service.

Common problems and fixes

The block editor stops working

The editor relies on inline scripts and sometimes on eval-like behaviour from plugins. Apply a lighter policy to wp-admin, or exclude it, and focus strict policies on the front end.

Google Fonts or icons disappear

Allow the font stylesheet domain in style-src and the font file domain in font-src. Better still, self-host fonts and remove both.

Analytics stops recording

Analytics tools need their script domain in script-src and their collection endpoint in connect-src. Check the provider’s documentation, because collection domains sometimes differ from script domains.

Embedded videos and maps are blank

Add the embed domain to frame-src.

Images from a CDN don’t load

Add the CDN domain to img-src.

CSP and payment card security

Newer payment card security requirements focus on controlling scripts on payment pages and detecting unauthorised changes. A CSP that restricts script sources and connect destinations on checkout, combined with monitoring of violation reports, is one of the most effective technical controls for this. It won’t replace your payment provider’s guidance on compliance, but it’s a strong building block.

Keep a written record of your policy

Document every source in your policy and the reason it’s allowed, such as “connect-src includes the analytics collection domain for site statistics”. When someone asks why a domain is allowed, or whether it can be removed, the answer is written down. It also makes reviews faster when plugins or services change.

Frequently asked questions

Will a CSP break my WordPress site?

It can if you enforce a strict policy without testing. Starting with safe directives and using report-only mode for the rest avoids breakage.

Is ‘unsafe-inline’ pointless?

Not entirely. With ‘unsafe-inline’, injected inline scripts can still run, but a strict connect-src and script-src still block external malicious scripts and data exfiltration to unknown domains. Nonces give stronger protection.

Do I need a plugin for CSP?

No. You can set CSP headers at the server or CDN level. Some security plugins help generate policies and nonces, which can be useful on complex sites.

How often should I review my policy?

Whenever you add or remove third-party services, after major plugin updates and periodically through violation reports.

Final thoughts

Content Security Policy is one of the strongest defences against injected scripts and data theft, but it rewards patience. Start with safe directives, build the full policy in report-only mode, inventory and trim your third-party sources, decide how to handle inline scripts, apply stricter rules on checkout and enforce only when reports are clean. Keep monitoring afterwards. Done gradually, CSP adds serious protection without breaking the site your visitors rely on.