Attach the Tagging Endpoint to Your Custom Domain
This step attaches a custom subdomain (e.g. m.example.com) to your Cloud Run tagging service so first-party event traffic flows through your domain instead of a generic *.run.app URL. Browsers treat events to a first-party subdomain as same-site, which keeps cookies and storage available — that's the whole point of server-side tagging.
Only the tagging service needs a custom domain. The preview service stays on *.run.app because GTM Preview connects to it through GTM's own UI and doesn't need first-party hosting. Activating LOCKS the primary GA4 property and GTM web container for this setup — to use a different GA4 or GTM later, archive this setup and start a new one. After Activate, the tagging endpoint URL is also pushed into the GTM server container's tagging-server URL so the GA4 client knows to expect requests at the new domain.
Happy path
- Step 1 — type the subdomain prefix (e.g.
m) into the input, click Save. - Step 2 — add a CNAME record at your DNS provider pointing the subdomain to
ghs.googlehosted.com. Click Test DNS until the result strip turns green. - Step 3 — click Activate, confirm the modal; GSS creates the Cloud Run domain mapping and sets the GTM server URL.
- Step 4 — click Check; all 5 requirement rows turn green (TLS may take 15+ minutes after Activate).
- Continue to Connect — wiring the web container, server container, and GTM preview test.
Glossary▶
- Custom domain
- The first-party subdomain you bind to the tagging Cloud Run service (e.g.
m.example.com). Browsers send GA4 events here; the tagging service serves them from your origin instead of Google's. - CNAME
- A DNS record type that points one hostname at another. For Cloud Run domain mappings, your subdomain CNAMEs to
ghs.googlehosted.com. - Domain mapping
- The Cloud Run resource that ties your custom domain to a specific Cloud Run service. Created via the Cloud Run v1 API (the v2 API doesn't expose this resource).
- eTLD+1
- Effective top-level domain plus one — the registrable root of a domain.
m.brand.comandbrand.comshare the same eTLD+1 (brand.com);brand.co.uk's eTLD+1 is alsobrand.co.uk, notco.uk. - SSL / TLS
- Google auto-provisions a managed SSL certificate after the domain mapping is created and DNS resolves correctly. Provisioning typically takes 15–60 minutes.
- Tagging-server URL
- The field on the GTM server container that lists where the container expects traffic (the
taggingServerUrlsarray under the hood). Activate sets this to["https://<custom-domain>"].
Not covered on this page: deploying Cloud Run services (Tagging Services), the GTM server container itself (GTM Containers), or DNS provider-specific instructions (consult your provider's docs for adding CNAME records).
Before you start
- Complete Tagging Services — the tagging service must be bound and the Step 3 runtime checks must be green.
- Have access to your domain's DNS settings (Cloudflare, Route 53, Google Domains, your registrar's control panel, etc.).
- The subdomain must share the same registrable root (eTLD+1) as your project's domain.
m.brand.comworks for project domainbrand.com;m.different-brand.comdoes not.
Decision record
Once a subdomain is saved, the page surfaces a Selected tagging endpoint decision record above the steps with the custom domain, the bound tagging service, the current Cloud Run domain mapping status, DNS state, and the requirement-check rollup.
| Field | Where it comes from | What "done" looks like |
|---|---|---|
| Custom domain | Step 1 below. | Subdomain saved (e.g. m.example.com). |
| Tagging service | Inherited from Tagging Services. | Tagging service name + *.run.app URL shown. |
| DNS | Step 2 — your DNS provider, plus GSS's Test DNS check. | CNAME resolves to ghs.googlehosted.com. |
| Domain mapping | Step 3 — Cloud Run v1 domain mappings + GTM server container URL. | Mapping active; GTM Server URL set to https://<custom-domain>. |
| Validation | Rollup of Step 4. | Verified after Step 4's Check button has run all 5 requirements green (may take 15+ minutes after Activate for TLS to provision). |
1 Which Subdomain Should Host First-Party Events?
Step 1 uses a split input: you type the prefix on the left and the page appends .<your-domain> on the right so the assembled subdomain is always on the same root as your project. The prefix pre-fills as m on a fresh project. Common alternatives: m, c, s, sst, stream. Avoid labels that ad blockers commonly filter (collect, tracking, analytics, pixel, stats) — events to those subdomains will be dropped at the browser before they reach Cloud Run.
Type your prefix and click Save. Save stays disabled until the prefix differs from what is already saved. The form posts the assembled custom_domain hidden field; on success the step re-renders with the new value as the selected subdomain.
Constraints (validated server-side):
- Must be a real subdomain (must contain a dot, must not be the root domain itself).
- Must share the same eTLD+1 as your project's domain —
m.brand.comworks for projectbrand.comorshop.brand.com, butm.different-brand.comdoes not. - No
https://prefix, no spaces, no slashes — just the bare prefix label (because the suffix is added automatically).
Clear subdomain
Once a subdomain is saved, a destructive zone at the bottom of Step 1 shows a Clear subdomain button gated behind an acknowledgement checkbox. Clearing resets every dependent piece of state in this step: DNS, TLS, the Step 4 requirement-check results, and the activated Cloud Run domain mapping in Step 3. After picking a new subdomain you'll need to re-activate to recreate the mapping. The Cloud Run domain mapping itself in GCP is replaced on the next Activate — not deleted by Clear.
2 Point Your Subdomain at Google's Edge
Step 2 stays locked until Step 1 has saved a subdomain. Once unlocked, the page shows the exact DNS record you need to create plus a schematic that highlights which pieces of the path are configured.
Create the DNS record
Add a CNAME record at your DNS provider:
The exact UI varies by provider. A short list of where the CNAME setting lives on the common ones:
- Cloudflare: Websites → your zone → DNS → Records → Add record. Set Type to CNAME. Set Proxy status to "DNS only" (grey cloud, not orange). A proxied record breaks Google's certificate provisioning.
- Route 53: Hosted zones → your zone → Create record. Record type CNAME. Routing policy Simple. TTL 300.
- Google Domains / Cloud Domains: DNS → Custom records → Manage custom records → Create new record. Type CNAME. Name = your prefix. Data =
ghs.googlehosted.com. - GoDaddy: My products → Domain → DNS → Add → CNAME. Host = your prefix. Points to =
ghs.googlehosted.com. - Namecheap: Domain List → Manage → Advanced DNS → Add New Record → CNAME Record. Host = your prefix. Target =
ghs.googlehosted.com. - Other registrars / DNS providers: look for the DNS / Records / Zone Editor screen and add a CNAME for your prefix that points to
ghs.googlehosted.com.(mind the trailing dot).
After saving the record, come back to the app and click Test DNS. The result strip next to the button reports pass/fail with the underlying message; the status pill above flips between Pending, Not pointing (DNS doesn't resolve to ghs.googlehosted.com yet), and DNS verified. DNS propagation is usually 1–5 minutes but can take up to 24 hours depending on your provider's TTL.
Cloudflare and other proxying providers
If your DNS provider is also a CDN/WAF (Cloudflare, Fastly, etc.), the orange-cloud / proxy / "Proxy" toggle MUST be off for this subdomain. With proxying enabled, the CNAME resolves to the CDN's IPs instead of ghs.googlehosted.com directly — which both fails the DNS check here and breaks Google's certificate provisioning. Set it to "DNS only" (grey cloud in Cloudflare) or its equivalent.
3 Map the Subdomain to the Tagging Service
Step 3 stays locked until a subdomain is saved in Step 1; the Activate button stays disabled until the GCP project, tagging service, and GTM server container are also bound. The step shows two inline status rows ("Domain mapping" and "GTM Server URL") that double as the "what does Activate do, and how far did the last run get?" surface so you can see at a glance which half is done.
Once DNS is green, click Activate. A confirmation modal explains that activating creates or updates live endpoint resources tied to your current primary GA4 property and GTM web container, and that those primary selections lock for this setup after activation. Click Confirm to run both operations together:
- Create the Cloud Run domain mapping via the Cloud Run v1 API. The mapping ties your subdomain to the tagging service in the same region. Google starts auto-provisioning a managed SSL certificate at this point — typically 15–60 minutes for the cert to be issued.
- Set the GTM server container's tagging-server URL to
https://<custom-domain>. This tells the GTM container to expect requests at the custom domain. GSS replaces any pre-existing URLs in the list with this single value.
Activate is idempotent: if the mapping already exists and points at the right service, it's left alone (treated as progress, not an error). If the mapping exists but points at a different service, GSS deletes the old one and creates a new one. Same for the GTM URL — if it already equals the expected value, no API write happens.
If exactly one of the two operations succeeds (for example, the domain mapping created but the GTM URL update failed), the step renders a partial-success callout instead of completing. The succeeded half stays applied; click Activate again to retry the failed half. The two status rows (Domain mapping / GTM Server URL) show which half is currently good.
If Activate is disabled, the page surfaces the missing prerequisite: GCP project not bound (complete GCP Project), tagging Cloud Run service not bound (complete Tagging Services), or GTM server container not bound (complete GTM Containers in Build). Resolve the prerequisite, then come back and Activate.
4 Are Domain, DNS, TLS, and GTM All Configured?
Step 4 stays locked until Activate has been attempted in Step 3 (a saved-but-never-activated domain has nothing to check yet). Once unlocked, click Check to run 5 requirement rows. GSS makes 1 HTTP request to /healthy, 1 TLS handshake check, 1 OS DNS lookup, 1 CNAME lookup, and 1 GTM API read.
4.1Tagging /healthy returns 200 via custom domain
The custom domain must respond to GET /healthy with HTTP 200 over HTTPS. This confirms DNS, SSL, and the Cloud Run service all line up end-to-end.
Where to check: open https://<your-tagging-domain>/healthy in a browser.
Fix: If you just activated, wait 15–60 minutes for SSL to provision and re-run Check. If it persists past an hour, see the deep dive — most causes are DNS-related.
Deep dive ▶
What it checks. HTTP GET to https://<custom-domain>/healthy returns status 200.
How GSS performs the check. One httpx.get call with verify enabled (validates the cert).
What counts as pass. Status 200.
Why it might fail. SSL still provisioning (most common immediately after activate). DNS not resolving from where the GSS server is running. Domain mapping wasn't created. Tagging service is down.
How to recover. Run all 5 checks; the failure pattern across them tells you where the problem is. Failure on 4.1 only (with 4.2–4.4 passing) means the Cloud Run service or domain mapping is the issue. Failure on 4.1 plus 4.2 typically means SSL is still provisioning.
4.2TLS certificate is valid
The TLS handshake to your custom domain must succeed without certificate errors. Google auto-provisions a managed SSL cert; this can take 15–60 minutes after Activate.
Where to check: open https://<your-tagging-domain> in a browser. The padlock should be solid; no certificate warning.
Fix: Wait. Provisioning is automatic and there's nothing you can do to speed it up. If it's been over an hour, check Cloud Run console → Domain mappings → your domain — there's a Certificate status indicator.
Deep dive ▶
What it checks. An httpx.get to the custom domain succeeds without raising RequestError or SSLError. The HTTP status code is not validated here — any successful TLS handshake passes (even a 5xx response).
How GSS performs the check. One httpx.get with TLS verification enabled.
What counts as pass. No SSL/TLS exception during the handshake.
Why it might fail. Cert still provisioning (most common). DNS provider proxying intercepts the cert (Cloudflare orange cloud). Custom cert misconfigured. The cert expired (rare for managed certs).
How to recover. Wait for provisioning, then re-run Check. If proxying is the issue (4.4 also fails), turn it off. The Cloud Run console's Domain mappings page surfaces a Certificate provisioned status on each row — that's the source of truth on whether Google's done.
4.3DNS resolves to an IP address
The custom domain must resolve to at least one IP address from the GSS server's perspective. If it doesn't resolve, the rest can't work.
Where to check: from your terminal, dig +short <your-tagging-domain> should return one or more IPs.
Fix: If the CNAME exists but doesn't resolve to an IP, your DNS provider may be having issues; wait a few minutes. If you literally have no DNS record yet, add the CNAME from Step 2.
Deep dive ▶
What it checks. socket.getaddrinfo(custom_domain, 443) from the GSS server returns at least one result.
How GSS performs the check. A standard OS DNS lookup using whatever resolver the GSS server is configured with. Not a recursive lookup of authoritative records — uses the local resolver.
What counts as pass. At least one address returned. The first IP is reported in evidence.
Why it might fail. No CNAME or A record exists. DNS hasn't propagated to the resolver the GSS server uses. Domain doesn't exist (NXDOMAIN).
How to recover. Confirm the CNAME exists at your DNS provider. From a terminal, dig +trace <domain> shows the full delegation chain. If it works locally but fails here, the GSS server's resolver is lagging — usually clears within a few minutes.
4.4DNS is not proxied (no CDN)
The custom domain's CNAME record must resolve directly to ghs.googlehosted.com. Anything else (proxied through a CDN, pointed at a load balancer IP, etc.) means Cloud Run can't validate the domain ownership and Google can't issue an SSL cert.
Where to check: dig CNAME <your-tagging-domain> should return exactly ghs.googlehosted.com. (note the trailing dot).
Fix: Set the CNAME at your DNS provider exactly to ghs.googlehosted.com. If you have a CDN proxying, disable proxy mode for this subdomain.
Deep dive ▶
What it checks. dnspython's CNAME query for the custom domain returns a target equal to ghs.googlehosted.com (after stripping the trailing dot).
How GSS performs the check. Direct DNS query via the dnspython library — bypasses the local resolver cache that 4.3 uses, so it sees authoritative records.
What counts as pass. CNAME target equals exactly ghs.googlehosted.com.
Why it might fail. CNAME points at a CDN/proxy (most common — Cloudflare orange cloud, Fastly, etc.). CNAME target is misspelled. No CNAME record at all (would also fail 4.3 — NoAnswer). Domain doesn't exist (NXDOMAIN — also fails 4.3).
How to recover. At your DNS provider, set the CNAME target exactly to ghs.googlehosted.com (or the equivalent UI: "Alias" / "ALIAS" / "CNAME"). Disable any proxy/CDN toggle for this subdomain.
4.5Server container URL set in GTM
The GTM server container's tagging-server URL must equal https://<your-tagging-domain>. The GA4 client and tags inside the container reference this URL.
Where to check: GTM → server container → Admin → Container Settings → Server container URL.
Fix: Click Activate in Step 3 — it sets this automatically. Or edit the field manually in GTM.
Deep dive ▶
What it checks. The GTM server container's taggingServerUrls array contains https://<custom-domain> exactly.
How GSS performs the check. One GTMProvider.get_container(server_path) call. Reads the taggingServerUrls field.
What counts as pass. Expected URL is in the list. The check is a list-contains test, not strict equality, so additional URLs in the list don't fail it (though Activate will overwrite to a single URL).
Why it might fail. Activate (Step 3) hasn't run yet. Activate succeeded for the domain mapping but the GTM URL update failed (partial success warning during Activate). User edited the field manually in GTM and removed the URL.
How to recover. Click Activate again — it's idempotent for the domain mapping, will retry the GTM URL update. Or set the URL manually in GTM Admin and re-run Check.
Common errors & failure modes
| Symptom | Likely cause | Where to fix |
|---|---|---|
| Subdomain entry | ||
| Save returns 422 "Domain must share the same root..." | eTLD+1 mismatch — the assembled subdomain's root differs from the project domain's root. | Use a prefix that combines with the project's domain to form a same-root subdomain. If you really need a different domain, change the project domain in Settings (more complex; involves rebinding the project to a different domain). |
| Save returns 422 "Enter a subdomain, not the root domain itself" | Prefix input was empty or evaluated to the bare project domain. | Type a prefix label (e.g. m) into the input before clicking Save. |
| Save returns 422 "Enter the domain only, without https:// prefix" or "no spaces or slashes" | Pasted a full URL or one with whitespace into the prefix input. | Strip the https:// and any trailing path or suffix. Just the bare prefix label. |
| DNS | ||
| Test DNS shows "Not pointing" | CNAME is set to something other than ghs.googlehosted.com. Often a CDN/proxy hosting the subdomain. |
DNS provider — set CNAME target exactly to ghs.googlehosted.com; disable proxy mode for the subdomain (Cloudflare grey cloud, etc.). |
| Test DNS shows NXDOMAIN | The CNAME hasn't been created yet, or it's pointed at a non-existent target. | DNS provider — confirm the CNAME exists. dig +trace <domain> to see the delegation chain. |
| Test DNS keeps showing old values after a DNS change | DNS propagation lag, or the resolver cache hasn't expired (depends on TTL). | Wait — typically 1–5 min, can be longer. Set lower TTL on future changes. |
| Activate | ||
| Activate button is disabled | A prerequisite is missing. The step surfaces which one ("Save a GCP project...", "Save the tagging Cloud Run service...", "Save the GTM server container...", or "Save the subdomain above..."). | Follow the inline link to the missing step, complete it, then come back and Activate. |
| Activate returns the partial-success callout | Either the domain mapping created but the GTM URL update failed, OR vice versa. | Click Activate again. It's idempotent — won't double-create — and will retry the half that failed. |
| Domain mapping creation 409 conflict | A mapping already exists for this domain (probably from an earlier failed attempt). | If it points at the right service: nothing to do, GSS treats it as success. If it points at the wrong service: GSS deletes and recreates. If the recreate keeps failing, manually delete the mapping in Cloud Run console. |
| Requirement checks | ||
| 4.1 + 4.2 fail right after Activate but 4.3 + 4.4 pass | SSL certificate is still provisioning. Normal immediately after Activate. | Wait 15–60 minutes. Check Cloud Run console → Domain mappings — there's a Certificate status indicator there. |
| 4.5 fails after partial-success Activate | Domain mapping went through but GTM URL update didn't. | Click Activate again to retry the GTM URL update. Or set the tagging-server URL manually in GTM Admin. |
| After changing the saved subdomain, Check fails until you re-Activate | Domain mapping in Cloud Run still points at the old subdomain. | Re-run Activate. GSS detects the mismatch and replaces the mapping. |
Next step
Once all 5 requirement rows pass and TLS is provisioned, the tagging endpoint is ready. From here you continue into Connect — binding the web and server GTM containers, running a preview test, and wiring the saved tagging domain into the GA4 client and the web container's GA4 tag.