focosys.io
← Writing
Essay

How to Set Up Server-Side GTM on Google Cloud (Step by Step)

A step-by-step walkthrough of server side GTM setup on Google Cloud, from provisioning the container to verifying tags fire from your own domain.

How to Set Up Server-Side GTM on Google Cloud (Step by Step)
Key takeaways
  • The whole point of sGTM is running your tagging server on a subdomain you own (like sgtm.yoursite.com), so Safari's ITP and ad blockers see first-party requests instead of a third-party call to googletagmanager.com.
  • Google Cloud Run auto-scales the sGTM container and only bills for requests processed, which runs $10-40/month for most mid-size sites instead of a fixed VM cost.
  • The step everyone skips is the CNAME record. Without it, your server-side setup is still calling a Google-owned domain and you get none of the ad-blocker resilience you set this up for.
  • Verify with curl and DevTools before touching a single tag. If the preview server shows a Google IP instead of your own, the container isn't actually receiving traffic yet.

A client came to me with a server-side GTM container already running. They'd followed a tutorial, clicked through the Cloud Run setup, deployed the container, and moved on. Six months later they wanted to know why their Facebook CAPI match quality was still "Fair" and why Safari traffic still looked hollowed out in GA4.

I opened their sGTM preview mode. The container was tagged, the client was configured, everything looked right in the GTM UI. Then I checked the actual request URL in the Network tab. It was hitting sgtm-abc123.run.app. Not their domain. A random Cloud Run URL Google assigns by default.

That single missed step meant every "server-side" request was still, from the browser's perspective, a third-party request to a run.app domain. Safari's ITP treats that exactly like a third-party pixel. Ad blockers with any DNS-level list catch it too. They'd built the entire architecture and skipped the one part that makes it worth building.

This is the actual setup, in order, including the part that gets skipped.

What You're Actually Building

Client-side GTM runs in the browser. Every tag (GA4, Meta Pixel, LinkedIn Insight) fires a request directly from the user's browser to that vendor's servers. Ad blockers and ITP both target exactly this pattern: request to a known third-party domain, from a page that isn't that domain.

Server-side GTM moves the tag execution to a container you run on Google Cloud. The browser sends one request, to your own subdomain, to your own tagging server. That server then forwards data to GA4, Meta, LinkedIn, wherever it needs to go, over server-to-server connections that browsers never see and blockers can't touch.

The infrastructure piece is Google Cloud Run: a serverless container platform that spins up your tagging server, scales it under load, and bills you per request instead of a fixed monthly VM fee.

Cost before you start

Cloud Run pricing is pay-per-use. A mid-size site (under 500K sessions/month) typically runs $10-40/month. High-traffic sites (multi-million sessions) can hit $150-300/month. There's no flat "server-side GTM costs $X" number, it scales with request volume.

Step 1: Set Up the Google Cloud Project

Step 1: Set Up the Google Cloud Project

Go to console.cloud.google.com. If you don't have a Google Cloud account tied to billing, create one now, this is separate from your GTM or GA4 login.

Create a new project. Name it something you'll recognize later, sgtm-prod or [clientname]-tagging. Don't reuse an existing project that has other infrastructure on it. Keeping the tagging server isolated makes billing and IAM permissions easier to audit later.

Enable billing on the project. Cloud Run won't deploy without an active billing account attached, even if you stay in the free tier.

Step 2: Create the Server Container in GTM

In your GTM account (tagmanager.google.com), click the container dropdown in the top left, then Create Container. Choose Server as the container type.

This creates a new server container separate from your web container. You'll get a container ID starting with GTM- just like the web one, but it lives in its own workspace.

GTM will show you a setup screen with a suggested Google Cloud project name and region. Pick the region closest to your primary user base, us-central1 for US traffic, europe-west1 for EU. This affects latency, not cost.

Click Automatically provision tagging server. This is the option that does the Cloud Run deployment for you. There's a manual path if you want to manage the Docker image yourself, but for a first setup, let Google provision it.

Step 3: Let Google Provision Cloud Run

GTM hands off to Google Cloud and deploys a Cloud Run service running the sGTM container image. This takes 3-5 minutes. When it finishes, you'll get a default URL that looks like:

https://sgtm-abc123xyz-uc.a.run.app

This URL works. Your container is live and reachable. But this is the exact URL from the story above, a run.app domain that browsers and blockers will treat as third-party traffic.

Do not point any tags at this URL yet. The next step is what makes the setup actually worth doing.

Step 4: Map a First-Party Subdomain

This is the step that gets skipped.

Pick a subdomain on your own domain. Something like sgtm.yoursite.com or metrics.yoursite.com. Avoid anything that screams "tracking", analytics.yoursite.com gets blocklisted by some ad-block filter lists just from the name.

In GTM, go to your server container's Admin tab, then Custom Domain. Enter the subdomain you picked. GTM will give you a set of DNS records to add, typically a CNAME pointing to a Google-managed target, plus a TXT record for domain verification.

Go to your DNS provider (Cloudflare, Route 53, whatever you use) and add those records exactly as given. Propagation takes anywhere from 10 minutes to 48 hours depending on your provider and TTL settings.

Type: CNAME
Name: sgtm
Value: [value provided by GTM, something like ghs.googlehosted.com]
TTL: 3600
Don't skip DNS verification

GTM will show the custom domain as "pending" until it verifies the TXT record and can provision an SSL certificate for your subdomain. This can take a few hours. Don't route production traffic to the container until the custom domain shows "Active" in the GTM UI. If you route traffic through the run.app URL in the meantime and forget to switch it, you've rebuilt the exact problem you started with.

Step 5: Verify the Container Is Actually Receiving Requests

Once the custom domain shows Active, don't trust the UI. Check it yourself.

curl -I https://sgtm.yoursite.com/healthz

You should get a 200 OK back. If you get a 404 or a connection error, the domain mapping isn't finished or the container isn't running.

Then open your site in Chrome, open DevTools, go to the Network tab, and trigger a page load. Filter for your subdomain. You should see a request to sgtm.yoursite.com/g/collect or similar, with a response from your own server, not a redirect to googletagmanager.com.

If the request is going to run.app instead of your subdomain, your web container's GA4 config tag is still pointing at the default server URL. Check the "Server Container URL" field in your GA4 Configuration tag in the web container, it needs to be updated to the custom domain.

Step 6: Point Your Web Container at the Server

In your web GTM container, open your GA4 Configuration tag. There's a field called Server Container URL. Set it to your new subdomain:

https://sgtm.yoursite.com

Do the same for any other tags you're migrating, Meta CAPI, Google Ads conversion tags that support server-side forwarding.

Publish the web container. Then go back to DevTools, reload the page, and confirm the outbound request now hits your subdomain with a 200 response and a valid payload in the request body.

Step 7: Add Clients and Tags in the Server Container

The server container needs at least one Client to accept incoming requests. GTM auto-adds a "GA4" client when you provision through the automatic setup, which listens for requests matching the GA4 Measurement Protocol format.

Add server-side tags to forward data onward:

  • GA4 tag: forwards events to Google Analytics using your Measurement ID.
  • Meta Conversions API tag: forwards events to Meta using your Pixel ID and access token from Events Manager.
  • Google Ads Conversion tag: forwards conversion events using your conversion ID and label.

Each of these needs to be configured with a trigger, same as client-side GTM. Preview mode works the same way too, click Preview in the server container, and you'll see incoming requests and which tags fired.

Step 8: Test With Preview Mode Before Publishing

Click Preview in the server container. This opens a debug view showing every request hitting your server, what client processed it, and which tags fired as a result.

Load your site in a separate tab with the web container also in preview mode. Trigger a page view, then an event (add to cart, form submit, whatever you're tracking). Watch both preview windows.

Confirm three things:

  1. The web container sends the event to your subdomain, not run.app.
  2. The server container receives it and the GA4 client processes it.
  3. The GA4 tag and any other configured tags fire and show a successful response, not a 400 or 500 error.

If the GA4 client shows "No matching client found," your incoming request format doesn't match what the client expects, usually a sign the web container is sending to the wrong path or the Measurement ID is misconfigured.

What to Check After You Publish

Give it 24-48 hours, then go back to GA4 Realtime and confirm event volume matches what you were seeing before the migration. A 10-20% dip in the first day is common while caches and DNS settle. A 50%+ drop means something in the client-tag mapping is broken and events aren't reaching the container at all.

Check Meta Events Manager under Test Events to confirm CAPI events are arriving with the expected match quality. If match quality dropped compared to the old Pixel-only setup, you're likely missing user data parameters (email, phone, external ID) in the server-side payload, that's a separate fix, but it won't show up unless you check.

The setup itself takes about an hour if DNS propagates fast. Verifying it actually changed anything, that request URLs are first-party, that ad-blocker-affected browsers are now reporting data, that Safari sessions look less hollowed out, takes another few days of watching the numbers. Do both. A container that's technically running but still pointed at a run.app URL gives you all the maintenance overhead of server-side tagging with none of the benefit.

Christopher Landaverde — Marketing Systems Engineer More writing →