A security review tightens your Content Security Policy. The site still loads, the pages look fine, and nobody raises an alarm. Two weeks later someone notices that conversions in Google Ads have fallen off a cliff and GA4 sessions are down by a third.
The cause is often a single header. A strict CSP tells the browser which domains it may load scripts from and send data to, and Google Tag Manager depends on several domains nobody thought to list. The browser does exactly what the policy says and blocks them, silently from the user's point of view.
Security and measurement are not in conflict here. They just need to be configured together. This is how CSP interacts with GTM, how to tell when it is the problem, and how to fix it without weakening the policy.
What is a Content Security Policy?
A Content Security Policy is a set of rules your site sends to the browser, listing which sources are allowed to load scripts, images, frames, and network connections. Anything not on the list is refused. It is one of the most effective defenses against cross site scripting and injected malicious code.
It is usually delivered as a Content-Security-Policy HTTP response header. It can also be set with a meta tag in the page head, though the header is more common and supports more features, such as reporting.
The policy is made of directives, each covering one type of resource. The ones that matter most for tracking are:
script-src controls where JavaScript can load from and whether inline scripts may run. The GTM container, every vendor library it loads, and every custom HTML tag fall under this directive.
connect-src controls where scripts can send data using fetch, XMLHttpRequest, and beacons. Most modern analytics hits, including GA4, travel this way.
img-src controls image sources. Many tracking pixels still send data by requesting a tiny image, so a missing domain here drops hits without any visible sign.
frame-src controls which pages may load in iframes. Some ad and conversion tags, and GTM's own noscript fallback, use frames.
If a directive is not set, the browser falls back to default-src. A policy with a tight default-src and no specific entries for tracking will block most of it.
Why does a strict CSP break Google Tag Manager?
Because GTM is designed to load code from many places, and a strict CSP is designed to refuse code from anywhere not explicitly approved. The container itself, each tag's library, each tag's data endpoint, and any inline code all need permission, and each one missing from the policy is a tag that fails.
Walk through a typical container and the list grows quickly:
The container. GTM loads from www.googletagmanager.com, and the standard install snippet is itself an inline script. Without that domain in script-src and a way to permit the inline snippet, nothing else in the container runs at all.
GA4. The Google tag loads from www.googletagmanager.com and sends hits to Google Analytics collection endpoints, including *.google-analytics.com, region1.google-analytics.com, and *.analytics.google.com. Those belong in connect-src, and some belong in img-src as well because of fallback transport.
Google Ads. Conversion and remarketing tags use www.googleadservices.com and googleads.g.doubleclick.net, and may also call www.google.com. These appear across script-src, img-src, connect-src, and frame-src depending on the tag.
Meta. The Meta Pixel loads its script from connect.facebook.net and sends events to www.facebook.com, which usually needs to be allowed in script-src, img-src, and connect-src.
Custom HTML tags. Every custom HTML tag injects a script into the page. Under a policy that blocks inline scripts, those tags simply do not execute. GTM custom JavaScript variables are a further case, because they rely on evaluating code, which a strict policy also blocks unless 'unsafe-eval' is allowed.
Preview mode. GTM's debug and preview tools load from additional Google domains. A policy that works in production can still break preview, which makes the container harder to troubleshoot.
The exact domain list changes as vendors update their infrastructure, so always confirm against each vendor's current documentation rather than copying a list once and forgetting it.
Open the browser console. When a CSP blocks a resource, the browser logs an error along the lines of "Refused to load the script" or "Refused to connect to", followed by the blocked URL and the directive that blocked it. That message tells you exactly what to add, and where.
Load the page with DevTools open, complete a test conversion, and read the console. Common forms are:
"Refused to load the script" means a domain is missing from script-src.
"Refused to connect to" means a domain is missing from connect-src, so the script loaded but its data never left.
"Refused to execute inline script" means inline code was blocked, typically the GTM snippet itself or a custom HTML tag.
The Network tab will show the same requests as blocked. GTM preview mode may show tags as fired even though their requests were refused, because GTM fired the tag correctly and the browser stopped what happened next. That mismatch, tags firing in preview but no data arriving in the platform, is a strong hint that CSP is involved.
Should I use a nonce or 'unsafe-inline'?
Use a nonce. Adding 'unsafe-inline' to script-src makes GTM work by allowing every inline script on the page, including any an attacker manages to inject, which removes most of the protection the policy exists to provide. A nonce allows only the scripts you mark.
A nonce is a random value your server generates fresh for every page response. It goes in the CSP header, for example 'nonce-r4nd0mV4lu3', and as a nonce attribute on each script you trust. The browser runs inline scripts whose nonce matches and refuses the rest. Because the value changes on every request, an attacker cannot guess it in advance.
Google's documented guidance for using Tag Manager with a CSP is to use a nonce on the container snippet rather than relaxing the policy. Its developer documentation covers the nonce-aware setup, how it applies to custom HTML tags, and which additional permissions custom JavaScript variables need. Follow the current version of that guidance, since it is updated as GTM changes.
The trade-offs are real. Nonces require server-side rendering or an edge layer that can generate and inject a new value per response, which is harder on heavily cached or fully static sites. 'unsafe-inline' is trivial to deploy and weakens the policy considerably. Some teams accept 'unsafe-eval' for custom JavaScript variables while keeping nonces for inline scripts, or rewrite those variables to avoid the need. That is a decision for your security team and your measurement team to make together, not for one of them to make alone.
How do I test a CSP change without breaking tracking?
Deploy it in report-only mode first. The Content-Security-Policy-Report-Only header applies the same rules without enforcing them: the browser loads everything as normal and reports what the policy would have blocked. You see the damage before any of it happens.
Run the report-only policy alongside your current one for a week or more, covering real traffic, every template, and every conversion path. Collect the violation reports, either through the console during manual testing or through a reporting endpoint defined in the policy. Add the legitimate tracking domains, confirm the reports go quiet, then switch the header to enforcing mode.
Repeat this whenever you add a new marketing tag. A new vendor added in GTM on Monday can be silently blocked by the enforcing policy by Monday afternoon, and nobody will know until the monthly report looks thin.
Does server-side tagging make CSP easier?
Yes, considerably. With server-side tagging on a first-party subdomain, the browser sends most tracking data to one address you own, such as data.yoursite.com, and your server forwards it to Google, Meta, and others from there. connect-src only needs to trust your own subdomain for those hits.
That shrinks the policy and makes it more stable. Instead of tracking the collection endpoints of every vendor and updating the policy whenever one changes, you maintain one entry you control. It also strengthens the security case, since fewer external domains are trusted to receive data from your pages.
It does not remove the need for a nonce or for script-src entries. The web container still loads in the browser, and any vendor script that must run client-side still needs permission. Server-side tagging simplifies the connection side of the policy, not the script side.
The practical takeaway
A Content Security Policy that blocks your tags is a policy working as designed on a configuration nobody finished. The fix is not to loosen it until everything passes. It is to list what your tracking legitimately needs, allow exactly that, and test in report-only mode before enforcing. Done properly, you keep the protection and the measurement, and your ad platforms keep receiving the conversions they bid on.
FAQ
What CSP domains does Google Tag Manager need?
At minimum, www.googletagmanager.com in script-src for the container, plus a nonce or equivalent permission for the inline install snippet. Every tag inside the container then adds its own requirements: GA4 needs Google Analytics collection endpoints in connect-src, Google Ads needs googleadservices.com and googleads.g.doubleclick.net, and Meta needs connect.facebook.net and facebook.com. Check each vendor's current documentation, because endpoints change over time.
GTM preview shows that GTM decided to fire the tag. It does not show whether the browser allowed the tag's network request. If your Content Security Policy is missing the vendor's domain in connect-src or img-src, the tag fires and the request is refused. Check the browser console for "Refused to connect to" errors naming the blocked URL.
Is 'unsafe-inline' safe to use for Google Tag Manager?
It works, but it weakens your policy substantially. 'unsafe-inline' allows every inline script on the page, including any injected by an attacker, which is the main threat a CSP exists to stop. Google's guidance for GTM is to use a nonce instead. If your site cannot generate nonces yet, treat 'unsafe-inline' as a temporary compromise and plan to replace it.
What is Content-Security-Policy-Report-Only?
It is a header that applies a policy in observation mode. The browser does not block anything, but it reports every resource the policy would have blocked, in the console and optionally to a reporting endpoint. It lets you test a stricter policy against real traffic and fix missing tracking domains before switching to enforcement, so conversions are never lost during the rollout.
Does server-side GTM remove the need for a Content Security Policy?
No. It simplifies the policy but does not replace it. With server-side tagging on a first-party subdomain, most tracking hits go to your own domain, so connect-src can be much shorter. The browser still loads the GTM web container and some vendor scripts, which still need script-src entries and nonce handling. You still need a CSP, just a simpler one.