Server-Side GTM: The Practical Guide for Agencies
If you run a marketing agency managing client tracking, you have almost certainly hit the same frustrations: adblockers silently stripping your conversion pixels, Safari killing cookies after 7 days, and clients asking why their Google Ads data doesn't match their CRM. Server-Side Google Tag Manager (sGTM) solves all three | and despite sounding intimidating, it is one of the highest-ROI moves an agency can make in 2026.
What Server-Side GTM Actually Does
In client-side GTM, every tag fires directly from the visitor's browser. This means adblockers block 15-30% of conversion events, Safari limits cookies to 7 days, and tag scripts add weight to every page load. Server-Side GTM flips the architecture: you run a GTM container on a cloud server, the browser sends a single request to your subdomain (e.g. measure.yourclient.com), and that server relays data to Google, Facebook, and other endpoints. Because the request goes to your domain rather than google-analytics.com, adblockers cannot block it. And because the server sets cookies via HTTP headers rather than JavaScript, Safari's 7-day limit does not apply.
What This Means For Your Agency
When you migrate a client to sGTM, three things improve immediately:
1. Conversion Data Becomes More Accurate
The 15-30% of events previously blocked by adblockers start flowing again. For an e-commerce client spending €50,000/month on Google Ads, recovering even 15% of lost conversions is the difference between a campaign that looks break-even and one that shows clear profitability.
2. Remarketing Audiences Grow
Because first-party cookies survive longer, remarketing lists in Google Ads and Facebook populate faster and stay larger. For B2B clients with long sales cycles, a visitor who browsed pricing pages 25 days ago is still in the audience when the sales team runs a retargeting campaign.
3. GDPR Compliance Becomes Easier
With sGTM, you control data before it leaves your server. You can strip PII (email addresses, phone numbers, IP addresses) before forwarding events to Google or Facebook | something impossible in client-side GTM where raw data leaves the browser directly.
How Much Does It Cost?
Google Cloud Run costs approximately €30-50/month for typical agency traffic volumes, plus a small server instance for preview mode. Total: roughly €50-80/month per client. For the data-quality improvement, this is one of the cheapest infrastructure upgrades an agency can make.
Getting Started: The 4-Step Setup
- Create a Google Cloud project | enable Cloud Run and required APIs (10 minutes).
- Create a GTM Server container | in Tag Manager, choose "Server" as the target. Google provides an auto-deploy script to Cloud Run.
- Point a subdomain at your cloud instance | map
measure.yourclient.comto the Cloud Run URL via DNS. - Migrate client-side tags | update GA4 and ads tags to send events to the server container, which forwards them onward.
The first setup takes about 2 hours if you have never done it. After one or two, it drops to 45 minutes.
Cost Breakdown: What You Actually Pay Per Client
The "€30-50/month for Cloud Run" figure we gave above is a starting point. In practice, costs vary based on traffic volume, how many tags you route through the server container, and which Google Cloud region you choose. Here is a more detailed breakdown drawn from real billing data across a dozen active client containers.
Cloud Run Compute Costs
Google Cloud Run charges per request and per compute second. For a typical agency client site doing 50,000-150,000 pageviews per month, the sGTM container processes roughly 200,000-500,000 requests (each page view generates multiple events: page_view, plus any custom events like form_submission, scroll, click). At Cloud Run's pricing of approximately €0.40 per million requests plus compute time at roughly €0.000024 per vCPU-second, a 150,000 pageview site running the standard 1 vCPU / 512MB configuration costs about €25-45/month. Lower-traffic sites (under 30,000 pageviews) often stay within Cloud Run's free tier , 2 million requests per month , meaning the compute cost is literally zero.
Additional Google Cloud Costs
The hidden costs are not the compute. They are the supporting services:
- Cloud Logging: Every request processed by sGTM generates log entries. For a medium-traffic site, logging can add €8-15/month. You can reduce this by adjusting log severity , set it to "Errors only" in production and you will cut logging costs by 70-80%.
- Load Balancer / Custom Domain Mapping: If you map a custom domain (like
measure.yourclient.com) to your Cloud Run service, Google provisions a load balancer. This adds roughly €15-20/month regardless of traffic. This is the fee that catches agencies off guard , it is not included in the Cloud Run calculator. - Preview Server: GTM Server containers require a separate instance for debug/preview mode. If you run this on Cloud Run as well, add another €10-15/month for the preview instance, though you can run it on a cheap Compute Engine micro instance (f1-micro, roughly €4/month) to save money.
Realistic total per client: For a site with 75,000 pageviews/month, a custom domain, and preview mode active, expect approximately €55-85/month. For a smaller site under the free tier with no custom domain, the cost is €0-15/month (preview server only). For a large e-commerce site doing 500,000+ pageviews/month, costs can reach €120-180/month , still a fraction of what that site spends on Google Ads, and easily justified by the 15-30% conversion recovery.
Alternative Hosting: Stape.io and Others
You do not have to use Google Cloud. Stape.io offers managed sGTM hosting with a simpler setup, built-in custom domain mapping, and a flat pricing model starting at roughly €20/month for up to 500,000 requests. For agencies that do not want to manage Google Cloud infrastructure, this is a legitimate alternative. The trade-offs: Stape adds latency (requests route through Stape's infrastructure before reaching Google), and you lose direct control over logging, scaling configuration, and region selection. For most agency clients, the convenience outweighs the latency trade-off , Stape's median added latency is under 100ms, which is imperceptible. We have used both approaches: direct Cloud Run for clients who need maximum control and lowest possible latency, Stape for smaller clients where the setup overhead is not worth the marginal cost savings.
DNS Setup: A Walkthrough That Does Not Assume You Know Networking
DNS configuration is where most sGTM deployments stall. It should not. Here is the step-by-step walkthrough, starting from zero networking knowledge.
Why you need a custom domain: Your sGTM container runs at a URL like https://gtm-server-abc123-uc.a.run.app. If you leave it at that default URL, browsers treat cookies set by this domain as third-party cookies , because run.app is not your client's domain. Safari will refuse to set them. Chrome's Tracking Protection will block them. The entire first-party cookie advantage of sGTM disappears. You must map a subdomain of your client's actual website domain to the Cloud Run service, so the browser sees the cookie as first-party.
Step 1: Verify Domain Ownership in Google Cloud
In the Google Cloud Console, navigate to "APIs & Services" and then "Domain verification." Add your client's root domain (e.g. yourclient.com). Google provides a TXT record value. Copy it.
Step 2: Add the Verification TXT Record
Log in to your client's DNS provider , this is typically where their domain was purchased or where their nameservers point (Cloudflare, GoDaddy, Namecheap, AWS Route 53, or similar). Add a new TXT record:
- Name/Host:
@(or leave blank, depending on your provider's convention , this means "the root domain") - Value: Paste the TXT value Google gave you
- TTL: 300 (5 minutes, for faster propagation during setup , you can increase this later)
Wait 5-10 minutes and click "Verify" in the Google Cloud Console. If it does not verify immediately, wait longer , DNS propagation can take up to 48 hours in theory, but 10-30 minutes is typical with a low TTL.
Step 3: Map Your Custom Domain to Cloud Run
In the Cloud Run service page for your sGTM container, go to the "Networking" tab. Under "Custom domains," click "Add mapping." Choose your verified domain from the dropdown and enter the subdomain you want (e.g. measure or ssgtm). Google will generate DNS records for you , typically two A records and two AAAA records pointing to Google's load balancer IP addresses.
Step 4: Add the DNS Records
Back in your DNS provider, create the records Google provided:
- Add an A record for
measure.yourclient.compointing to the first IP address - Add a second A record for
measure.yourclient.compointing to the second IP address - Repeat for the AAAA records (IPv6). If your DNS provider does not support AAAA records or your client's visitors are primarily on IPv4, the AAAA records are optional , the setup will work with A records only.
Some DNS providers (Cloudflare, in particular) require you to disable the proxy (the orange cloud icon) for these records. Cloud Run's custom domain mapping needs direct DNS resolution, not Cloudflare's CDN proxy. If the orange cloud is enabled, SSL certificate provisioning will fail and your container will be unreachable on the custom domain.
Step 5: Wait for SSL Provisioning
Google Cloud automatically provisions a managed SSL certificate for your custom domain. This takes 10-30 minutes. You can check the status in the Cloud Run "Networking" tab under "Custom domains." Once the certificate status shows "Active," your sGTM container is live at https://measure.yourclient.com. Test it by visiting the URL , you should see a blank page (the sGTM container does not return HTML by default), not an SSL error.
Step 6: Update Your Client-Side GTM Configuration
In your client-side GTM container, update any tags that send data to the server container (GA4, Google Ads, Facebook) to point to https://measure.yourclient.com instead of the default run.app URL. This is the final step that makes cookies first-party. Verify with Tag Assistant: after a page load, check the Application tab in Chrome DevTools , you should see cookies under measure.yourclient.com, not under run.app.
Troubleshooting Common Deployment Issues
Based on debugging sGTM deployments across two dozen clients, here are the problems we actually encounter and how to fix them.
Issue: Container Returns 403 or "No healthy upstream"
Your Cloud Run service is not running or has scaled to zero. Cloud Run scales container instances to zero after a period of inactivity (about 15 minutes by default). The next request triggers a cold start, which takes 2-5 seconds. If your client's site has low traffic, every visitor may hit a cold start. The fix: set min-instances to 1 in your Cloud Run service configuration. This costs roughly €8-12/month extra (a single instance running 24/7 at minimum spec), but eliminates cold starts entirely. For client-facing production containers, this is worth it , a 5-second delay on the first analytics hit of a visitor's session is not acceptable.
Issue: Cookies Not Being Set in Safari
You mapped a custom domain, but Safari still blocks the cookies. Check two things. First, is the subdomain a child of the main site domain? measure.yourclient.com is a subdomain of yourclient.com, so cookies should work. But if you used a completely different domain (e.g. yourclient-analytics.com), that is a different site in Safari's eyes and cookies are third-party. Second, check that your sGTM container is setting cookies with SameSite=None; Secure when needed, and that all traffic is over HTTPS. Safari is strict about this , cookies without the Secure flag on an HTTPS site are silently dropped.
Issue: Data Mismatch Between Client-Side and Server-Side GA4
You are running both client-side and server-side GA4 in parallel (as you should during migration), but the two reports do not match. This is normal , they should not match exactly , but the server-side count should be higher, not lower. Common causes of server-side undercounting:
- Transport URL is wrong: The client-side GA4 tag is still sending hits to
www.google-analytics.cominstead of your server container URL. Check your GA4 tag configuration in GTM , the "Transport URL" field should point to your server container's/g/collectendpoint. - Server container is dropping events: Check the sGTM container's error logs in Cloud Logging. Look for events being blocked by a client (tag) that has a trigger condition that is too restrictive. For example, if your server-side GA4 client only listens for events from specific IP ranges and your client's traffic includes mobile users on different ranges, those events are silently dropped.
- Consent signals are not being forwarded: If your server container receives consent signals but a server-side tag has consent checks enabled and the consent is "denied," the event will not be forwarded to Google. This is correct behaviour , but if your client-side reporting shows more conversions than server-side, it usually means your client-side consent check is missing or broken. Fix the client-side consent, not the server-side filter.
Issue: Preview Mode Is Not Working
GTM's preview mode for server containers works differently from client-side. You need to add ?gtm_debug=xxxxx to the server container URL in your debug configuration, not just to the client-side URL. In the GTM server container, go to "Preview," copy the debug URL, and paste it into your client-side GTM container's server container URL field (append ?gtm_debug=xxxxx to the transport URL). Then refresh your site with the client-side GTM preview active. Both client and server events should appear in the respective preview panels. If they do not, check that your browser is not blocking third-party cookies , even in preview mode, GTM uses cookies to maintain the debug session.
Issue: Cloud Run Costs Unexpectedly High
A client's sGTM bill comes in at €200 this month when it was €40 last month. Check three things. First, did someone deploy a change that's generating loops? A common mistake: a server-side GA4 tag fires a conversion event back to the client, which triggers another server call, which fires another conversion, and so on. Check your event counts in Cloud Run's metrics dashboard , if requests-per-second has spiked without a corresponding traffic spike, you have a loop. Second, check Cloud Logging costs , verbose logging is the silent budget-killer. Third, check if min-instances was accidentally set higher than 1 during a configuration change. A setting of min-instances: 3 triples your base compute cost.
Issue: Facebook CAPI Events Not Matching
If you route Facebook Conversions API (CAPI) events through sGTM, a frequent issue is event deduplication. Facebook expects you to send the same event from both the browser pixel and the server (CAPI) with matching event_name and event_id, and it deduplicates on event_id. If your sGTM container generates a different event ID than the browser pixel, Facebook counts the event twice. The fix: pass a consistent event ID from the client-side GTM to the server container. Use a random ID generated once per event in the client-side dataLayer and forward it to the server container as an event parameter. The server-side Facebook tag then uses that same ID.
North Digital has built server-side containers for agencies across Europe | get in touch and we will have yours running by the end of the week.