Content-Security-Policy (CSP) is an HTTP header that protects your site against XSS attacks and other threats. However, an overly restrictive CSP can block Google services like GTM, Analytics, or Maps. This guide shows how to configure CSP so all necessary services work.

What is Content-Security-Policy?

CSP defines allowed sources for different types of resources (scripts, styles, images, fonts). The browser blocks everything that is not explicitly allowed.

Basic Syntax

Content-Security-Policy: directive source1 source2; directive2 source3;

Main Directives

DirectiveControls
default-srcDefault source for all types
script-srcJavaScript scripts
style-srcCSS styles
img-srcImages
font-srcFonts
connect-srcXHR, fetch, WebSocket
frame-srcIframes
object-srcPlugins (Flash, Java)

Source Values

ValueMeaning
'self'Same domain
'none'Block everything
'unsafe-inline'Allow inline (dangerous!)
'unsafe-eval'Allow eval() (dangerous!)
'nonce-xyz'Only scripts with this nonce
'strict-dynamic'Trust scripts loaded by trusted ones
https:All HTTPS sources
*.example.comSubdomain

CSP for Google Tag Manager

GTM requires many domains and functionalities. Configuration depends on mode (client-side vs server-side).

GTM Client-Side - Minimal Configuration

Content-Security-Policy:
  script-src 'self' 'unsafe-inline' 'unsafe-eval'
    https://www.googletagmanager.com
    https://tagmanager.google.com;
  img-src 'self' data:
    https://www.googletagmanager.com
    https://www.google-analytics.com
    https://ssl.gstatic.com
    https://www.gstatic.com;
  connect-src 'self'
    https://www.google-analytics.com
    https://analytics.google.com
    https://stats.g.doubleclick.net
    https://region1.google-analytics.com;
  frame-src
    https://www.googletagmanager.com;

Problem: GTM Requires unsafe-inline and unsafe-eval

GTM dynamically injects scripts, which requires 'unsafe-inline'. Custom HTML tags may use eval(). This significantly weakens CSP.

Solution 1: Nonce for GTM

<!-- Generate nonce server-side -->
<script nonce="abc123">
  (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
  new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
  j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;
  j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
  j.nonce='abc123';
  f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-XXXXX');
</script>
Content-Security-Policy:
  script-src 'self' 'nonce-abc123' 'strict-dynamic'
    https://www.googletagmanager.com;

Solution 2: Server-Side GTM

Server-side GTM significantly simplifies CSP:

Content-Security-Policy:
  script-src 'self' 'nonce-abc123'
    https://gtm.yourdomain.com;
  connect-src 'self'
    https://gtm.yourdomain.com;

CSP for Google Analytics 4

GA4 with gtag.js

Content-Security-Policy:
  script-src 'self'
    https://www.googletagmanager.com;
  img-src 'self'
    https://www.google-analytics.com
    https://www.googletagmanager.com;
  connect-src 'self'
    https://www.google-analytics.com
    https://analytics.google.com
    https://region1.google-analytics.com
    https://region2.google-analytics.com
    https://region3.google-analytics.com;

GA4 with GTM

If you load GA4 through GTM, you need a combination of the above.

CSP for Google Maps

Maps JavaScript API

Content-Security-Policy:
  script-src 'self' 'unsafe-inline'
    https://maps.googleapis.com
    https://maps.gstatic.com;
  img-src 'self' data: blob:
    https://maps.googleapis.com
    https://maps.gstatic.com
    https://*.ggpht.com
    https://*.google.com
    https://*.googleapis.com;
  style-src 'self' 'unsafe-inline'
    https://fonts.googleapis.com;
  font-src 'self'
    https://fonts.gstatic.com;
  connect-src 'self'
    https://maps.googleapis.com
    https://places.googleapis.com;
  frame-src
    https://www.google.com;
  worker-src blob:;

Maps Embed API (iframe)

Content-Security-Policy:
  frame-src
    https://www.google.com
    https://maps.google.com;

CSP for Google Fonts

Content-Security-Policy:
  style-src 'self'
    https://fonts.googleapis.com;
  font-src 'self'
    https://fonts.gstatic.com;

Alternative: Self-hosting Fonts

Hosting fonts locally eliminates external dependencies:

Content-Security-Policy:
  font-src 'self';
  style-src 'self';

CSP for Google reCAPTCHA

reCAPTCHA v2/v3

Content-Security-Policy:
  script-src 'self'
    https://www.google.com/recaptcha/
    https://www.gstatic.com/recaptcha/;
  frame-src
    https://www.google.com/recaptcha/
    https://recaptcha.google.com;
  style-src 'self' 'unsafe-inline';

CSP for YouTube Embeds

Content-Security-Policy:
  frame-src
    https://www.youtube.com
    https://www.youtube-nocookie.com;
  img-src 'self'
    https://i.ytimg.com
    https://img.youtube.com;

CSP for Google Ads

Content-Security-Policy:
  script-src 'self' 'unsafe-inline'
    https://www.googleadservices.com
    https://www.googletagmanager.com
    https://googleads.g.doubleclick.net;
  img-src 'self'
    https://www.google.com
    https://googleads.g.doubleclick.net
    https://www.googleadservices.com;
  connect-src 'self'
    https://www.google.com
    https://pagead2.googlesyndication.com;
  frame-src
    https://bid.g.doubleclick.net
    https://tpc.googlesyndication.com;

Google AdSense

Content-Security-Policy:
  script-src 'self' 'unsafe-inline' 'unsafe-eval'
    https://pagead2.googlesyndication.com
    https://adservice.google.com
    https://www.googletagservices.com
    https://partner.googleadservices.com;
  img-src 'self' data:
    https://pagead2.googlesyndication.com
    https://tpc.googlesyndication.com
    https://*.google.com;
  frame-src
    https://googleads.g.doubleclick.net
    https://tpc.googlesyndication.com
    https://www.google.com;
  style-src 'self' 'unsafe-inline';

Complete Configuration for a Typical Site

Site with GTM, GA4, Maps, and Fonts

Content-Security-Policy:
  default-src 'self';

  script-src 'self' 'unsafe-inline' 'unsafe-eval'
    https://www.googletagmanager.com
    https://tagmanager.google.com
    https://www.google-analytics.com
    https://maps.googleapis.com
    https://maps.gstatic.com;

  style-src 'self' 'unsafe-inline'
    https://fonts.googleapis.com
    https://tagmanager.google.com;

  img-src 'self' data: blob:
    https://www.googletagmanager.com
    https://www.google-analytics.com
    https://ssl.gstatic.com
    https://www.gstatic.com
    https://maps.googleapis.com
    https://maps.gstatic.com
    https://*.ggpht.com
    https://*.google.com;

  font-src 'self'
    https://fonts.gstatic.com;

  connect-src 'self'
    https://www.google-analytics.com
    https://analytics.google.com
    https://region1.google-analytics.com
    https://stats.g.doubleclick.net
    https://maps.googleapis.com;

  frame-src
    https://www.googletagmanager.com
    https://www.google.com
    https://www.youtube.com;

  object-src 'none';
  base-uri 'self';
  form-action 'self';

Implementing CSP

Apache (.htaccess)

Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com; ..."

Nginx

add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com; ..." always;

Node.js (Express)

const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "https://www.googletagmanager.com"],
    styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
    imgSrc: ["'self'", "data:", "https://www.google-analytics.com"],
    fontSrc: ["'self'", "https://fonts.gstatic.com"],
    connectSrc: ["'self'", "https://www.google-analytics.com"],
    frameSrc: ["https://www.googletagmanager.com"]
  }
}));

Netlify (netlify.toml)

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; script-src 'self' https://www.googletagmanager.com; ..."

Vercel (vercel.json)

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; script-src 'self' https://www.googletagmanager.com; ..."
        }
      ]
    }
  ]
}

Testing CSP

1. Report-Only Mode

Start with reporting mode, which doesn’t block but only logs:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report;

2. Browser DevTools

Console will show CSP errors:

Refused to load the script 'https://example.com/script.js' because it violates the Content Security Policy directive: "script-src 'self'".

3. CSP Evaluator

https://csp-evaluator.withgoogle.com/ - Google’s tool for CSP analysis.

4. Observatory by Mozilla

https://observatory.mozilla.org/ - comprehensive security audit.

CSP Violation Reporting

Reporting Endpoint

Content-Security-Policy: default-src 'self'; report-uri /csp-report; report-to csp-endpoint;

Receiving Reports (Node.js)

app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  console.log('CSP Violation:', req.body['csp-report']);
  res.status(204).end();
});

Best Practices

1. Start Restrictively

Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self';

Add sources only when needed.

2. Avoid unsafe-inline and unsafe-eval

If possible, use nonce or hash instead of 'unsafe-inline':

<script nonce="random123">
  // Your code
</script>
Content-Security-Policy: script-src 'nonce-random123';

3. Use strict-dynamic

'strict-dynamic' allows trusted scripts to load subsequent ones:

Content-Security-Policy: script-src 'nonce-abc123' 'strict-dynamic';

4. Consider Server-Side GTM

Server-side tagging drastically simplifies CSP and increases security.

5. Update Regularly

Google may change domains. Monitor CSP reports and update the policy.

Summary

CSP for Google Services requires balancing security and functionality:

  1. GTM requires compromises - 'unsafe-inline' is often necessary
  2. Server-side GTM significantly simplifies CSP
  3. Nonce + strict-dynamic is best practice
  4. Test in Report-Only before deployment
  5. Monitor violations through reporting

A well-configured CSP protects users against XSS without blocking site functionality.

Sources

  1. MDN - Content-Security-Policy https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

  2. Google - CSP for Google Tag Manager https://developers.google.com/tag-platform/tag-manager/csp

  3. Google - CSP Evaluator https://csp-evaluator.withgoogle.com/

  4. web.dev - Content Security Policy https://web.dev/articles/csp

  5. Google Maps Platform - CSP https://developers.google.com/maps/documentation/javascript/content-security-policy