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
| Directive | Controls |
|---|---|
default-src | Default source for all types |
script-src | JavaScript scripts |
style-src | CSS styles |
img-src | Images |
font-src | Fonts |
connect-src | XHR, fetch, WebSocket |
frame-src | Iframes |
object-src | Plugins (Flash, Java) |
Source Values
| Value | Meaning |
|---|---|
'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.com | Subdomain |
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
Google Ads Conversion Tracking
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:
- GTM requires compromises -
'unsafe-inline'is often necessary - Server-side GTM significantly simplifies CSP
- Nonce + strict-dynamic is best practice
- Test in Report-Only before deployment
- Monitor violations through reporting
A well-configured CSP protects users against XSS without blocking site functionality.
Sources
-
MDN - Content-Security-Policy https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
-
Google - CSP for Google Tag Manager https://developers.google.com/tag-platform/tag-manager/csp
-
Google - CSP Evaluator https://csp-evaluator.withgoogle.com/
-
web.dev - Content Security Policy https://web.dev/articles/csp
-
Google Maps Platform - CSP https://developers.google.com/maps/documentation/javascript/content-security-policy



