Server-side GTM first-party and transport URL reference
last verified · against Google server-side Tag Manager documentation as of 2026-07
Server-side GTM first-party transport reference: server_container_url wiring, GA4 client identification, FPID cookies, custom domains, CSP, and self-hosting.
What this is
Server-side Tag Manager routes browser event traffic to a container running on
your own infrastructure, where a built-in Google Analytics: GA4 client claims
the requests and forwards them to Google. The single wiring point is the
web-side Google tag’s server_container_url config option, set to the server
container’s Default URL.
Web-side wiring
| Item | Value | Where set |
|---|---|---|
| Config option | server_container_url |
gtag.js config / web container Google tag Configuration settings |
| Value | Server container Default URL | Copied from the server container |
| Purpose | Redirects GA4 /g/collect-style browser traffic to the server container |
Single wiring point |
The GA4 v2 /g/collect-style requests are redirected to the server container,
where the built-in Google Analytics: GA4 client claims and forwards them. In
Tag Manager terminology, clients are adapters between the software running on a
user’s device and your server container.
GA4 server client identification
| Mode | Identifier source | Cookie | Accessibility | Lifetime |
|---|---|---|---|---|
| JavaScript Managed | Incoming &cid parameter |
_ga |
Browser-readable | Subject to browser caps |
| Server Managed | FPID cookie, preferred over incoming &cid |
FPID | HttpOnly (server-only) |
Extended to ~2 years (practitioner-sourced) |
- The documented GA4 walkthrough sets Cookies and Client Identification to JavaScript Managed.
- Under Server Managed, the client parses the identifier from the FPID
cookie and uses that value to set the Client ID in the outgoing request to
Google. Under JavaScript Managed, the client uses the incoming
&cidparameter and does not create an FPID cookie. - The FPID cookie is written via the
Set-CookieHTTP response header with theHttpOnlyflag, so it is not accessible to browser JavaScript — only the web server can read it. - If the Name field in the server-managed cookie settings is left blank,
the client will not write the FPID cookie and will not read an existing FPID
cookie to generate
client_id(effectively cookieless).
The ~2-year FPID lifetime, plus its SameSite/Secure attributes, come from
practitioner sources (InfoTrust, Simo Ahava), not from an official Google page.
Verify the exact default in the GA4 client’s server-managed cookie settings
in-product.
GA4 server client activation
| Activation option | Effect |
|---|---|
| Default GA4 paths | Captures standard GA4 event / collect requests |
| Default gtag.js paths for specific IDs | Serves the gtag.js JavaScript library through your server container URL |
The debug view shows an incoming request as collect?v=2&.... The full verbatim
list of default request paths the GA4 server client claims (commonly cited as
/g/collect, /collect, /gtag/js?id=TAG_ID) is not confirmed word-for-word
on an official page; treat the extended list as practitioner-sourced.
Custom-domain hosting options
| Option | Example | Requirement | Context |
|---|---|---|---|
| Same-origin (recommended) | https://www.example.com/metrics |
CDN or load balancer to forward requests | First-party |
| Subdomain | https://metrics.example.com |
DNS entry updates | First-party |
| Default | *.run.app |
None | Third-party |
- For a true first-party context the tagging server and the website must run on the same domain; only then do you unlock durable cookies and the security benefits.
- If you use the default
*.run.appdomain you can only set JavaScript-accessible cookies and lack access to the security and durability benefits of first-party cookies.
Transport methods and required headers
| Transport | Notes |
|---|---|
| Image pixel | Pixel-based delivery |
| Fetch API | Fetch request |
| XHR | XMLHttpRequest |
| Service worker in iframe | Iframe loaded from your server container domain |
| Header | Directive | Must include | Reason |
|---|---|---|---|
| Content-Security-Policy | img-src |
[SERVER_CONTAINER_URL] |
Measurement requests |
| Content-Security-Policy | connect-src |
[SERVER_CONTAINER_URL] |
Measurement requests |
| Content-Security-Policy | frame-src |
[SERVER_CONTAINER_URL] |
Service-worker iframe transport |
| X-Frame-Options | — | Must not block iframes from [SERVER_CONTAINER_URL] |
Service-worker iframe transport |
For app / measurement-protocol style requests the pixel URL uses your custom
server-container hostname — the Google endpoint hostname is replaced by your
server container domain, e.g. https://custom.example.com/app?v=1&tid=TAG_ID&cid=555&t=screenview.
Dependency serving (Google tag gateway)
| Item | Value |
|---|---|
| What it does | Serves Google scripts such as gtm.js from your first-party infrastructure instead of Google’s servers |
| Prerequisites | Server container + tagging server + configured custom server domain |
| Serving option 1 | CDN (recommended) |
| Serving option 2 | The tagging server itself |
Self-hosted (manual) provisioning
| Item | Value |
|---|---|
| Docker image | gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable |
CONTAINER_CONFIG |
Server container configuration string — required on every instance |
RUN_AS_PREVIEW_SERVER=true |
Marks an instance as a preview server |
PREVIEW_SERVER_URL |
HTTPS URL of the preview server — required on each SST cluster instance |
| Default port | 8080, overridable with the PORT environment variable |
| Liveness | /healthy endpoint — a non-healthy response means restart the server |
Both CONTAINER_CONFIG and PREVIEW_SERVER_URL are obtained via Manually
provision tagging server in Tag Manager, and every instance must use the same
values.
Gotchas
- Server-to-server / measurement-protocol requests never appear in the Preview timeline. Cause: a request only shows if it carries the preview cookie or the
X-Gtm-Server-Previewheader, which non-browser requests lack. Fix: add theX-Gtm-Server-Previewheader (copied from the preview mode overlay) to the outbound request. (Header wording is practitioner-sourced; verify against the live debug docs.) - Only the first / config event reaches the server endpoint. Cause: setting the transport on the GA4
configevent alone does not reliably propagate to every subsequent event. Fix: setserver_container_urlon the Google tag / all events, then verify each event’s destination in preview. - GA4 /
_gacookies reset roughly every 7 days for Safari, and returning users are counted as new. Cause: the default*.run.app(third-party) domain or JavaScript-managed cookies are JavaScript-set and hit ITP’s ~7-day cap. Fix: map a first-party custom domain and use Server Managed (FPID,HttpOnly) identification. - FPID / server-managed identification misbehaves with multiple GA trackers or across domains. Cause: FPID is single-cookie oriented and was not designed for multiple trackers or cross-domain use. Fix: use one GA4 client identity per FPID cookie; for cross-domain use the GA linker / FPLC mechanism rather than FPID alone.
- Measurement requests to a same-origin path (e.g.
/metrics) return 404 or never reach the tagging server. Cause: same-origin hosting needs a CDN or load balancer rule to forward that path. Fix: configure the CDN / load balancer to route the chosen path prefix to the tagging server. - The browser blocks the service-worker/iframe transport or measurement fetch requests. Cause: CSP or
X-Frame-Optionson the main site does not allow the server container URL. Fix: add[SERVER_CONTAINER_URL]toimg-src,connect-src, andframe-src, and ensureX-Frame-Optionsdoes not block iframes from it.
Quick recipes
Wire the web tag to the server container:
// Web container Google tag / gtag.js config optiongtag('config', 'G-XXXXXXX', { server_container_url: 'https://metrics.example.com'});Allow the server container in your Content Security Policy:
Content-Security-Policy: img-src 'self' https://metrics.example.com; connect-src 'self' https://metrics.example.com; frame-src 'self' https://metrics.example.comRun a self-hosted tagging server (main instance):
docker run -p 8080:8080 \ -e CONTAINER_CONFIG='<config string from Tag Manager>' \ -e PREVIEW_SERVER_URL='https://preview.example.com' \ gcr.io/cloud-tagging-10302018/gtm-cloud-image:stableRun the companion preview server:
docker run -p 8080:8080 \ -e CONTAINER_CONFIG='<config string from Tag Manager>' \ -e RUN_AS_PREVIEW_SERVER=true \ gcr.io/cloud-tagging-10302018/gtm-cloud-image:stableCheck liveness:
curl -f https://metrics.example.com/healthy || echo "restart the server"Route a measurement-protocol request through the debug session:
curl "https://custom.example.com/app?v=1&tid=TAG_ID&cid=555&t=screenview" \ -H "X-Gtm-Server-Preview: <token from the preview mode overlay>"Related
Sources
Official pages these values were read from (read 2026-07-20):
- An introduction to server-side tagging (read 2026-07-20)
- Configuring the GA4 data stream with server-side tagging (read 2026-07-20)
- Custom domain configuration (read 2026-07-20)
- Send data to server-side Tag Manager (read 2026-07-20)
- Load Google scripts first-party (dependency serving) (read 2026-07-20)
- Manually set up and update a tagging server (read 2026-07-20)
- Preview and debug server containers (read 2026-07-20)
- Google tag API reference (gtag.js) (read 2026-07-20)
FPID behaviour, the ~2-year lifetime, and the X-Gtm-Server-Preview header
wording are corroborated by practitioner sources rather than an official Google
page, and are flagged above for in-product verification:
- The FPID Cookie for Google Analytics in server-side tagging — Simo Ahava (read 2026-07-20)
- Server-side client configuration demystified — InfoTrust (read 2026-07-20)
Two things the docs do not settle, flagged rather than guessed: the tagging
server’s CORS header behaviour for cross-origin fetch/XHR transport is not
spelled out on the official pages (the docs address it indirectly via the
same-origin recommendation and the connect-src requirement), and Consent Mode
interaction with the server_container_url wiring — whether consent state is
forwarded and how it gates FPID writing — is not detailed; confirm both against
the relevant product documentation before relying on them.
Changelog
- — Initial version, verified against official documentation.