A client migrated to server-side GTM in Q1. Their reasoning: sGTM routes traffic through their own server, so the browser-based consent problem goes away. No more ITP cookie limits, no more ad blockers eating the Facebook pixel, no more Consent Mode pings reducing GA4 event volume.
Three weeks after launch, their DPO asked for the tracking documentation for an upcoming audit. I pulled the sGTM container and found every tag firing with full user data, regardless of what the CMP banner said. Consent Mode was still installed on the web container. It just wasn't connected to anything downstream.
This mistake is common right now because "server-side" and "consent-compliant" get talked about like they're the same upgrade. They're not. Server-side tracking is an infrastructure change. Consent Mode is a permissions layer. Moving your tags to a server doesn't remove the legal requirement to gate them, it just moves the point where gating has to happen.
What Each System Actually Does
Consent Mode v2 is a signaling protocol. When a user interacts with your CMP banner, Google's consent APIs (ad_storage, ad_user_data, ad_personalization, analytics_storage) get set to granted or denied. Google tags (GA4, Google Ads, Floodlight) read these signals and adjust behavior: full tracking, cookieless pings, or nothing at all.
Server-side tracking is a data routing change. Instead of the browser calling google-analytics.com or graph.facebook.com directly, events go to your own server (a Google Cloud Run instance, typically), which then forwards data to the ad platforms on your behalf.
Neither one enforces the other automatically. Consent Mode governs what the browser sends. Server-side tracking governs where that data travels afterward. If you get consent right on the client but never check it on the server, you've built a fast pipe for shipping data you had no right to send.
Moving a tag from the web container to the server container strips its automatic Consent Mode integration unless you explicitly rebuild it. Google's client templates for GA4 and Ads don't universally propagate consent state server-side the way they do client-side. You have to check consent in the server container yourself.
Where the Signal Actually Has to Travel
Here's the full path, and where it breaks in practice:
- User loads the page. CMP banner renders before any tag fires.
- User makes a choice. CMP calls
gtag('consent', 'update', {...}). - Web GTM tags check consent state before firing. Tags with proper Consent Mode configuration hold or fire pings-only versions.
- If you're on server-side GTM, the web container's client (usually the GA4 client or a custom client) sends the event to your server endpoint.
- The server container needs to know what consent state applied to that specific event.
Step 5 is where almost every broken setup fails. The consent decision was made in step 2. By the time the event lands in your server container in step 5, that context has to be carried along in the payload, not re-derived server-side. Your server has no cookie to read. It only has whatever the browser sent it.
The Fix: Pass Consent State as Event Data
In your GA4 configuration tag (or whatever client-side tag initiates the hit), the consent state Google Tag already knows about gets included automatically in the outgoing request to your sGTM endpoint, as query parameters like gcs (Google Consent Status) and gcd (Google Consent Diagnostics).
Your server container's GA4 client parses these automatically if you're using Google's built-in GA4 client template. Check this in your server container:
GTM Server Container → Clients → GA4 Client
Open the client. Confirm "Enable consent mode" (or equivalent setting depending on template version) is turned on. If you built a custom client instead of using Google's, you have to parse gcs/gcd yourself and expose the values as event data fields so downstream tags can read them.
For non-Google tags (Meta CAPI, LinkedIn, TikTok server events), there's no built-in consent parsing. You have to do this manually.
// In your sGTM server container, a custom variable or transformation
// reading consent state passed from the client
const consentState = {
ad_storage: events.get('x-ga-gcs')?.includes('G1') ? 'granted' : 'denied',
analytics_storage: events.get('x-ga-gcs')?.includes('G1') ? 'granted' : 'denied'
};
return consentState.ad_storage;
Then gate your Meta CAPI tag, LinkedIn tag, or any non-Google destination tag on this variable, exactly like you'd gate a client-side pixel on a CMP consent category.
Reject all cookies on your site. Then check your Cloud Run logs (Logging → Logs Explorer, filtered to your sGTM service) for the resulting request. If you still see a fully-populated Meta CAPI payload with fbp, fbc, and hashed PII going out, your server container isn't respecting the consent signal it received.
Basic vs. Advanced Consent Mode Changes What You're Filtering
This matters more once you're server-side, because the two modes produce structurally different payloads for your server to handle.
Basic Consent Mode blocks Google tags from firing entirely until consent is granted. No cookies set, no pings sent, no server-side event at all for that user until they opt in. Your server container never sees denied-consent traffic from Google tags. Simple, but you lose all modeling inputs; Google has nothing to base conversion modeling on for that session.
Advanced Consent Mode allows tags to fire in a cookieless "ping" mode even when consent is denied. No cookies are set, no PII is collected, but Google gets a signal (device/browser info, no persistent identifier) that an event happened. These pings do reach your server container if you're using sGTM.
If you're on Advanced Mode, your server container will receive events tagged as consent-denied. You need explicit logic to make sure these events:
- Still get counted in GA4 (for conversion modeling to work, Google needs the aggregate signal)
- Never get forwarded to Meta CAPI, LinkedIn, or any platform where you don't have a legal basis to send them
This is the part teams miss. They correctly configure Advanced Consent Mode for GA4's benefit, then wire their server container to forward "all GA4-bound events" to every other ad platform tag without checking whether that specific event was consent-denied. The ping data meant only for Google's aggregate modeling ends up as a full CAPI event with hashed email and phone number attached, because someone copied the "forward everything" logic from the Google tag to the Meta tag without adjusting the consent check.
Auditing Your Own sGTM Container for This
Fifteen minutes, no external tools needed:
Open your server container in GTM. Go to Tags. For each destination tag (Meta CAPI, LinkedIn, TikTok, Pinterest, custom HTTP requests), check the firing triggers. Is there a consent-based condition, or does it fire on every incoming event?
Check variable usage. Search the container for any variable referencing
gcs,gcd, or a custom consent field. If your only consent-aware tag is the GA4 one, and everything else fires unconditionally, that's your gap.Reject consent on the front end and watch the server logs. Cloud Run → your sGTM service → Logs. Filter for the event you just triggered. Confirm which destination tags actually fired versus which ones should have been blocked.
Check your Meta CAPI tag configuration specifically. This is the highest-risk tag because it sends hashed PII (email, phone) by design. If it's not consent-gated, you're sending EU user data to Meta's servers with no valid consent basis at all, which is a GDPR problem independent of whether Meta itself handles it correctly downstream.
If you're running server-side tracking on a healthcare site, the consent-gating gap and the PHI-in-payload problem compound. A CAPI tag firing without consent checks is bad under GDPR. The same tag firing with condition names or provider specialties in the URL is a HIPAA violation on top of it. Fix the consent gate first, then check what's actually in the payload.
The Takeaway
Server-side tracking doesn't replace consent gating. It relocates it. The consent decision still happens in the browser, at the CMP. Your job is making sure that decision travels with the event all the way to your server container, and that every destination tag in that container checks it before firing, not just the Google ones that come pre-wired to do so.
If you migrated to sGTM expecting it to solve your consent problem, go check your Meta and LinkedIn tags right now. They're probably firing on everyone.