Content-Security-Policy Header Missing: How to Add It

No Comments
Content-security-policy header missing: how to add it

TL;DR: A Content-Security-Policy header restricts which origins can load scripts, styles, and other resources on your page. It's a security hardening measure, not a ranking factor — but a hacked site tanks rankings fast, so treat CSP as cheap insurance. Test in report-only mode, then enforce.

What a missing Content-Security-Policy header means

A Content-Security-Policy (CSP) response header tells the browser which origins are allowed to load scripts, styles, images, and frames on your page, so if it's missing the browser trusts everything and a single injected <script> can run unchecked. This is a defense-in-depth measure against cross-site scripting and content injection — it is not a direct Google ranking factor, and no page has ever ranked lower for lacking one.

So why does an SEO crawler flag it? Because a compromised page is an SEO problem by proxy. Injected spam, a hacked-site warning in Search Console, or a browser interstitial will bury your rankings faster than any header ever could. CSP is the seatbelt: invisible until the day it saves you.

A real example and the fix

Run a header check against a page with no policy and you get nothing back — here's a live response from a site that shipped without one:

$ curl -sI -A 'Mozilla/5.0' https://example.com/ | grep -i content-security
# (no output — header absent)

The fix is to send a policy that names your legitimate sources. A pragmatic starting policy for a typical WordPress site with a couple of third-party scripts looks like this — note it mirrors the shape of what large sites actually ship (GitHub and Cloudflare both send default-src plus explicit allow-lists):

Content-Security-Policy: default-src 'self';
  script-src 'self' 'unsafe-inline' https://www.googletagmanager.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' data:;
  connect-src 'self' https://www.google-analytics.com;
  frame-ancestors 'self';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests

After deploying, re-check and confirm the header now returns:

$ curl -sI -A 'Mozilla/5.0' https://example.com/ | grep -i content-security
content-security-policy: default-src 'self'; script-src 'self' ...

CSP directives that matter most

DirectiveControlsSane starting value
default-srcFallback for anything not named'self'
script-srcWhere JS can load from'self' + your tag manager
style-srcWhere CSS can load from'self' 'unsafe-inline'
img-srcImage origins'self' data: https:
frame-ancestorsWho can iframe your page (clickjacking)'self'
upgrade-insecure-requestsRewrites http:// sub-resources to https://enabled

How to detect it

  1. curl the header directly: curl -sI -A 'Mozilla/5.0' https://yourdomain.com/ | grep -i content-security-policy. No line back means no policy.
  2. securityheaders.com: paste your URL and read the grade. A missing CSP knocks the score down and is called out explicitly in the report.
  3. Browser DevTools: open the Network tab, click the document request, and read the Response Headers pane. CSP violations also log to the Console in red.
  4. Screaming Frog: enable Security in the crawl config; the Security tab flags every URL missing a CSP across the whole site at once.

How to fix it

Pick the layer you control and send the header there. Test in report-only mode first so you don't break your own page.

Apache (.htaccess)

<IfModule mod_headers.c>
  Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; img-src 'self' data: https:; frame-ancestors 'self'; upgrade-insecure-requests"
</IfModule>

Nginx

add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; img-src 'self' data: https:; frame-ancestors 'self'; upgrade-insecure-requests" always;

WordPress (no server access)

If you can't touch Apache or Nginx config, send the header from PHP in your theme's functions.php or a small must-use plugin:

add_action('send_headers', function () {
  header("Content-Security-Policy: default-src 'self'; "
    . "script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; "
    . "img-src 'self' data: https:; frame-ancestors 'self'; "
    . "upgrade-insecure-requests");
});

A security plugin such as one that manages HTTP headers can do the same through a UI if you'd rather not edit code. Either way the header ends up on the response — the browser doesn't care how it got there.

Report-only first (do this before enforcing)

Swap the header name for Content-Security-Policy-Report-Only. The browser logs every violation to the console but blocks nothing, so you can watch your real traffic surface the origins you forgot — a font CDN, an embedded map, a chat widget — then add them and only then switch to the enforcing header. Enforce a policy you haven't watched in report-only mode and you will white-screen your own site. Ask me how I know.

Common mistakes

A handful of errors account for most broken CSP rollouts:

  • Forgetting your own subdomains. If your CDN or asset host is cdn.yourdomain.com, 'self' won't cover it — name it explicitly.
  • Blocking inline styles the theme needs. Many themes inject inline CSS; omit 'unsafe-inline' from style-src and your layout collapses.
  • Setting the header twice. A plugin and the server both sending CSP produces two headers, and browsers enforce the intersection — the strictest combination, which usually breaks something.
  • Copying a huge policy off another site. GitHub's or Cloudflare's policy fits their assets, not yours. Start minimal and widen from report-only data.

FAQ

Will adding a CSP improve my Google rankings?

No, not directly. Google has never listed CSP as a ranking signal. The SEO payoff is indirect: it hardens the site against the kind of hack that does tank rankings via manual actions and malware warnings.

What's the difference between CSP and HSTS?

CSP controls what content is allowed to load; HSTS forces the connection itself to stay on HTTPS. They solve different problems and you want both.

Will a CSP break my existing scripts?

It can, which is exactly why you run Content-Security-Policy-Report-Only first. Anything not on your allow-list gets logged, you add it, and you flip to enforcing once the console is quiet.

Is 'unsafe-inline' bad?

It weakens the policy because it re-allows inline scripts, which is what most XSS abuses. It's a common concession for WordPress themes that inline JS. Tightening to nonces or hashes is the mature next step, but a policy with 'unsafe-inline' still beats no policy.

Do I need a CSP if I already run HTTPS everywhere?

Yes. HTTPS encrypts the transport; it does nothing to stop a malicious script that's legitimately served over that encrypted connection. See our mixed content guide and the security issues glossary for how these layers stack.

Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.

About SEO ProCheck

Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.

Work With Me

Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.

Subscribe to our newsletter!

More from our blog