Why Your sGTM Container Isn't Receiving Traffic (And It's Not What Anyone Online Will Tell You)

Why Your sGTM Container Isn't Receiving Traffic (And It's Not What Anyone Online Will Tell You)

Attribution & Analytics
May 12, 2026

Six hours of debugging. Eight disproved theories. The root cause turned out to be a Google Tag implementation detail buried in no public documentation: the transport URL is determined at first dispatch and locked thereafter. If Google Tag initialises under denied consent, your sGTM server URL is never applied. And the fix isn't what GTM's official docs suggest.

If you've shipped server-side Google Tag Manager (sGTM) onto a site that also runs a cookie banner and Consent Mode v2, and your Server Container is receiving zero traffic despite the browser dataLayer pushes firing correctly, this post is for you.

The short version

Aspect What's happening
Symptom GA4 events fire with full consent granted, but every request goes to www.google-analytics.com instead of your sGTM custom domain. Server Container receives nothing.
Root cause Google Tag's transport URL is determined at first-dispatch time. If consent is denied at that moment, transport locks to Google's cookieless infrastructure. Later consent updates don't change it.
The fix Read consent cookie synchronously before GTM loads, set the correct gtag('consent', 'default') from the start. Reload after first-time consent change so the cookie is set before Google Tag fires.
Why no one tells you this It's not in Google's official sGTM documentation, Analytics Mania, Stape, or Microsoft Learn. Major CMPs like Cookiebot and OneTrust implement this pattern but don't explain the why.

The setup that should have worked

Before the symptom, here's what was already in place. If your stack matches this and you're seeing zero sGTM traffic, you're in the same trap.

  • Next.js 16 site with a GTM Web Container installed via the standard NEXT_PUBLIC_GTM_ID pattern
  • Server-side GTM on Cloud Run, custom domain gtm.example.com with valid SSL
  • GA4 Configuration Tag (Google Tag) with server_container_url set to https://gtm.example.com as a Configuration parameter
  • Consent banner using Consent Mode v2: default-denied on first paint, update on accept
  • GTM Preview Mode showing tags firing successfully with consent granted

This is the practitioner-recommended configuration. Every sGTM tutorial online describes exactly this setup. Stape's docs, Simo Ahava's blog, Google's own sGTM documentation. None of them mention that this pattern fails when combined with a default-denied consent banner.

The symptom in detail

Open Chrome DevTools, Network tab, filter collect. Refresh the page. Accept the cookie banner. Trigger a GA4 event.

Expected: requests to https://gtm.example.com/g/collect?...

Actual: every single request goes to https://www.google-analytics.com/g/collect?... with gcs=G111 (consent fully granted), full client ID set, all the right parameters. Just the wrong destination.

The Server Container dashboard shows zero events. GTM Debug View shows tags firing with green checkmarks. Nothing about the failure surfaces in any official debugging tool.

Eight theories. All wrong.

What follows is the debug path in chronological order. Each theory was tested with a clean Chrome incognito session. None of them resolved it.

# Theory Test Result
1 Measurement ID override on GA4 Event tags is bypassing the Google Tag config Cleared the Measurement ID field on every Event tag Tags broke entirely. Reverted.
2 Missing explicit transport_url parameter Added transport_url=https://gtm.example.com alongside server_container_url No effect. Requests still routed to google-analytics.com.
3 Duplicate gtag('config') calls overwriting the transport setting Filtered dataLayer for all config calls Empty. No duplicates.
4 CDN propagation lag on the GTM container Forced republish, waited 30 minutes, verified container hash at runtime Latest hash live. No effect.
5 Duplicate GA4 runtime loading from another source Searched codebase for @next/third-parties, @vercel/analytics, hardcoded gtag.js, secondary gtag('config') calls Clean. No duplicate runtimes.
6 External gtag.js library loading separately Network tab filtered for googletagmanager.com Zero loads of the standalone library. Only GTM container.
7 Explicit Google Tag binder field on the Event tags Looked for "Google Tag" binder dropdown in current GTM UI The field doesn't exist in the current UI. Old documentation.
8 Variable-First Pattern with a Configuration Settings Variable Wrapped Google Tag config in a Configuration Settings Variable, applied to all GA4 tags No effect. Same routing behaviour.

By theory 8 the working assumption was that something was fundamentally broken inside GTM itself. Reading Google's documentation more carefully wasn't going to resolve it because the answer wasn't there.

What's actually happening

Google Tag's transport routing is locked at first-dispatch time. The moment Google Tag fires its first event (typically the page_view on the Initialization trigger), it inspects the current Consent Mode state and decides where to send data.

Per Consent Mode v2 design: when consent is denied at dispatch time, Google Tag enters cookieless modelling mode. Cookieless pings go to Google's centralised infrastructure at www.google-analytics.com. The server_container_url configuration is not consulted in cookieless mode, because Google manages cookieless modelling on their own servers, not yours.

Once that first dispatch happens, the transport URL is cached for the rest of the page session. Subsequent gtag('consent', 'update') calls flip the consent state in the dataLayer, but they do not re-derive the transport URL. Every event for the rest of that page load goes to the URL determined at first dispatch.

Step by step for the failure case:

  1. Page loads. Consent default = denied (the standard recommended configuration).
  2. GTM container loads, Google Tag fires on the Initialization trigger.
  3. Google Tag inspects consent: denied. Routes to www.google-analytics.com per cookieless modelling.
  4. Transport URL is now locked for this page session.
  5. React mounts ~200 to 400ms later. ConsentBanner reads localStorage, finds prior consent, pushes gtag('consent', 'update', { granted }).
  6. Consent state in dataLayer flips to granted.
  7. But Google Tag's transport path is already locked to google-analytics.com.
  8. Every subsequent event for this page load routes to Google, not your Server Container.
  9. sGTM at gtm.example.com receives zero traffic. Forever, on every page load.

The fix

Read the consent cookie synchronously before GTM loads. Set the correct gtag('consent', 'default') from the start, using the stored consent state. If consent was previously granted, Google Tag's first dispatch sees granted state, picks the sGTM transport URL, and locks correctly.

Here is the implementation pattern. Drop this as an inline script in your <head>, before the GTM container loads.

(function() {
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}

  function getStoredConsent() {
    try {
      var name = 'your_consent_cookie=';
      var decoded = decodeURIComponent(document.cookie);
      var parts = decoded.split(';');
      for (var i = 0; i < parts.length; i++) {
        var c = parts[i];
        while (c.charAt(0) === ' ') c = c.substring(1);
        if (c.indexOf(name) === 0) {
          var val = JSON.parse(c.substring(name.length));
          if (val && val.version === 1) {
            return {
              analytics: val.analytics === true ? 'granted' : 'denied',
              marketing: val.marketing === true ? 'granted' : 'denied'
            };
          }
        }
      }
    } catch (e) {}
    return null;
  }

  var stored = getStoredConsent();

  gtag('consent', 'default', {
    'analytics_storage':     stored ? stored.analytics : 'denied',
    'ad_storage':            stored ? stored.marketing : 'denied',
    'ad_user_data':          stored ? stored.marketing : 'denied',
    'ad_personalization':    stored ? stored.marketing : 'denied',
    'functionality_storage': 'granted',
    'security_storage':      'granted',
    'wait_for_update':       500
  });

  gtag('set', 'ads_data_redaction', !stored || stored.marketing === 'denied');
})();

One companion piece is needed for first-time visitors: a soft reload after they click Accept on the banner. The flow is:

  1. First-time visitor lands. No cookie yet. Sync-consent reads nothing, sets default denied.
  2. Google Tag fires under denied consent, transport locks to cookieless.
  3. Visitor clicks Accept. ConsentBanner writes the consent cookie.
  4. Page reloads. Sync-consent fires again, this time reads the cookie, sets default granted.
  5. Google Tag fires under granted consent, transport locks to sGTM. From this point on, every event hits your Server Container.

Returning visitors skip the reload entirely. The cookie is already set, sync-consent reads it on first load, transport locks correctly from the start.

Why this matters for anyone running sGTM with a cookie banner

The static default-denied inline script is the pattern recommended in nearly every Consent Mode v2 tutorial published online. Pair it with sGTM and you ship a configuration that looks correct in GTM Preview, looks correct in the dataLayer, looks correct in Network requests (they're 200-OK), but routes zero traffic to your Server Container.

This isn't a niche edge case. Any B2B site that ships sGTM and respects regional privacy law via a cookie banner hits this. Which is to say: every modern B2B site.

The reason this doesn't get documented is that the major CMP vendors (Cookiebot, OneTrust, Iubenda) bundle the sync-consent pattern into their integrations by default. Their customers never see the failure mode because the vendor solved it without explanation. If you build your own consent banner, you build directly into the trap.

The checklist

If you're implementing sGTM with Consent Mode v2, or debugging an existing setup that's not receiving server traffic, work through this list before anything else.

  1. Use a cookie-driven sync-consent inline script. Read stored consent before GTM loads. Set gtag('consent', 'default') from the cookie state, not statically denied.
  2. Soft-reload after first-time consent acceptance. The first session for a brand-new visitor will lock transport to cookieless. A page reload after the cookie is written transitions them into the returning-visitor flow correctly.
  3. Don't trust GTM Preview Mode for transport routing. Preview shows tag dispatch state, not the destination URL. A tag firing green doesn't mean it's reaching your Server Container.
  4. Don't spend hours on config-level fixes. If events are going to www.google-analytics.com instead of your sGTM domain, the cause is almost certainly transport locking under denied consent. Go straight to sync-consent.
  5. Verify in Network, not Debug View. Filter for collect, confirm the destination domain matches your sGTM custom domain on a fresh incognito page load.

What this discovery cost

Six hours of debugging. Eight theories tested and disproved. Two different LLMs (ChatGPT and Gemini) consulted at various points, each suggesting variations of the same already-tried configuration changes.

The breakthrough came from reading Consent Mode v2's design assumption literally: cookieless pings go to Google, not to your Server Container. That assumption combined with first-dispatch locking explained every symptom.

The fix shipped in one commit. Production verification confirmed it: GA4 events route through the custom sGTM domain, LinkedIn and Meta tags fire correctly on first page after reload, all downstream attribution wiring works.

This entire debugging cycle is now a 10-minute fix for anyone who reads this post first.


Building an attribution backend that has to survive consent mode? We've shipped this exact pattern, end to end, on production B2B funnels. The flagship engagement is a Demand Engine Diagnostic that maps your full GTM motion in one to two weeks.
See how Gilgamesh engineers GTM →