Recover data lost to Safari's 7-day cookie cap and ad blockers. Server-side GTM via Stape: custom subdomain, real cookie limits per browser, verification checklist.

1. Why client-side GA4 fails in 2026

Three different mechanisms get blamed for the same symptom. They are not the same thing, and only one of them shortens your GA4 cookie:

  • Safari ITP caps script-writable first-party cookies — anything set through document.cookie, which includes _ga — to 7 days, and to 24 hours when the visitor lands with tracking parameters from a domain WebKit has classified as a tracker. This is the one that actually kills your cookie.
  • iOS 17+ Link Tracking Protection does not touch cookie lifetime. It strips click IDsgclid, fbclid, ttclid and friends — out of URLs opened from Mail, Messages and Private Browsing. What you lose is the click-to-conversion join, which is why you capture and persist click IDs server-side.
  • Firefox ETP partitions third-party storage and blocks known tracker hostnames. Your first-party JS cookies survive; the request to googletagmanager.com frequently does not.

Ad blockers are the fourth and separate problem: uBlock Origin, AdGuard and Brave filter googletagmanager.com and google-analytics.com at the network layer, so gtm.js never executes and no amount of tag configuration helps.

What that costs, measured across the e-commerce audits I run — GA4 compared against the Shopify or Stripe backend over matched 7-day windows. This is my own data, not a published study:

  • 20–35% fewer Purchase events in GA4 than in the store backend
  • Consistently worse on iOS-heavy traffic, where the 7-day cap and in-app browsers compound
  • Inflated user counts, because a capped cookie turns the same person into a new user on their next visit

Server-side moves the tag from the browser to your server. The browser sends one hit to ss.yourdomain.com — a first-party hostname most filter lists don't carry — and your server forwards to GA4. See my sGTM service for done-for-you setup.

2. What is server-side GTM (first-party)

Think Browser → ss.jhasanr.top (sGTM container) → GA4 / Meta CAPI / Google Ads. The browser never talks to Google directly. Benefits:

FeatureClient GTMsGTM (first-party)
Cookie lifetime7 days in Safari (24h with link decoration)Up to 400 days in Chrome; still 7 days in Safari on a CNAME setup — see §4
Blocked by ad blockersYes — gtm.js is on every major filter listFar less often, but not immune: uBO and AdGuard uncloak CNAMEs
Data controlExposed in browserFiltered server-side
CostFree~€20/mo (Stape)

Alternative to Stape: self-host on Google Cloud Run — cheaper at scale but you manage updates. For 95% of stores, Stape is 3-hour setup.

3. Stape setup in 3 hours (exact steps)

3.1 Create sGTM container

  1. GTM → Admin → Create Server container → Manually provision.
  2. Stape.io → Create container → paste Container Config.
  3. Add custom subdomain: ss.jhasanr.top → in Stape, Add custom domain, then create exactly the DNS records Stape shows you for that domain. If you find advice telling you to CNAME ss at ghs.googlehosted.com, that is for a container hosted on Google App Engine — it will not work with Stape.

3.2 Web GTM → forward to server

// Web GTM: GA4 Config tag

Transport URL: https://ss.jhasanr.top

Server Container URL: https://ss.jhasanr.top

// Check "Send to server container"

3.3 Server GTM → GA4 Client & Tag

  • Clients: GA4 Client (claim /g/collect)
  • Tags: GA4 Tag → Measurement ID G-XXXX
  • Power-ups: Cookie Keeper — re-issues the GA cookie from the server with a Set-Cookie header instead of document.cookie

Deploy. Test via Stape Preview + GA4 DebugView — you should see events with client_ip = server IP but user_id preserved.

Two cookies, two different rule sets. This is where most write-ups — including an earlier version of this one — get it wrong:

  • _ga is written by gtag.js using document.cookie. It cannot be HttpOnly, because the JavaScript that reads it would break. It is script-writable, so Safari caps it at 7 days no matter what your server does.
  • FPID is written by the server container in a Set-Cookie response header and is HttpOnly. sGTM derives client_id from FPID, so identity survives even after _ga has been wiped. That is the actual mechanism behind "server-side extends your cookie".

What each browser then honours:

  • Chrome caps any cookie's Expires/Max-Age at 400 days (Chrome 104 and later). A server-set FPID gets the full 400 — you are hitting a browser ceiling, not a feature Stape adds.
  • Safari/WebKit caps server-set cookies from a CNAME-cloaked subdomain at 7 days too. A plain ss.yourdomain.com → *.stape.io CNAME is exactly the pattern that rule targets.
  • To beat 7 days in Safari the endpoint has to be genuinely same-origin: Stape's Own CDN / same-origin option, or a reverse proxy on your own infrastructure exposing the container as a path on the main domain.

Verify in DevTools → Application → Cookies. In Chrome you should see FPID present, HttpOnly ticked, expiry roughly 400 days out.

Key takeaway: "server-side gives you 400-day cookies" is a Chrome-only statement. Promise it to a client whose traffic is majority iOS and you will not be able to show it in DevTools. Set the expectation as: full lifetime in Chrome, 7 days in Safari unless you go same-origin.

5. Verification checklist (get GA4 within 3% of your backend)

  • ✓ Stape preview shows 200 OK for /g/collect
  • ✓ GA4 Realtime + DebugView fires purchase with same transaction_id as Shopify
  • ✓ Compare 7 days: GA4 purchases vs backend. Delta < 3% = pass
  • ✓ Test with uBlock Origin ON — hits still arrive
  • ✓ Chrome: FPID present, HttpOnly, expiry ~400 days
  • ✓ iPhone Safari: expect _ga to read 7 days. That is correct behaviour on a CNAME setup — check that FPID is still being sent rather than chasing the expiry date

Failing? Common pitfall: Web GTM still sends to googletagmanager.com — remove old GA4 tag, keep only server-forwarding tag. See my Meta CAPI guide for deduplication pattern.

FAQ

Does sGTM slow down site?

Usually it helps, because you replace five or more browser tags with one hit to your own subdomain. But measure it rather than assuming — the server hop adds latency to that one request, and a badly placed container region can cost you more than the tags did.

Cost at 500k hits/month?

Stape Business is around €50/mo up to 1M requests. Self-hosting on Cloud Run is roughly €10 at that volume but you own auth, scaling and container updates. Check current pricing before you quote it to a client.

GDPR compliant?

Server-side does not create a lawful basis. You still need consent before the first hit leaves the browser: Consent Mode v2 gates analytics_storage, ad_storage, ad_user_data and ad_personalization, and your server container has to honour those signals rather than forwarding regardless. Hosting in the EU reduces transfer exposure; it does not make an unconsented hit lawful. Log the consent state alongside the event. This is implementation guidance, not legal advice — confirm your setup with your own counsel.

FAQ

Does sGTM slow down site?

Usually helps, because you replace 5+ browser tags with one hit to your own subdomain. But measure — server hop adds latency and a badly placed container region can cost more than tags did.

Cost at 500k hits/month?

Stape Business ~€50/mo up to 1M requests. Self-hosted Cloud Run ~€10 at that volume but you own auth, scaling, updates. Check current pricing.

GDPR compliant?

Server-side does not create lawful basis. You still need consent before first hit: Consent Mode v2 gates analytics_storage/ad_storage/ad_user_data/ad_personalization and server must honour signals.

How to cite (LLM-friendly)

Jahid Hasan — "GA4 Server-Side Tracking: Bypass Ad Blockers & iOS 17+ Limits" — https://jhasanr.top/blog/ga4-server-side-tracking-ios17/ (Published 2026-08-15, Modified 2026-09-04)

Need this done for you?

I implement this in hours, verified against your backend — sGTM, CAPI, Enhanced Conversions.

Schedule quick call →