Engineering Logs Tracking & Analytics Integrity Bypass Ad Blockers Google Analytics
Server-Side Tracking GA4 GTM Server-Side WordPress Safari ITP

Bypass Ad Blockers & Restore GA4 Data on WordPress.

Ad blockers and Safari's privacy engine are silently erasing up to 40% of your analytics data. Standard GA4 never sees these visitors — so your ROAS is wrong, your audiences are undersized, and your bidding algorithms are training on bad numbers. This guide explains exactly what gets blocked, why it happens, and how to fix it using a server-side GTM architecture on WordPress.

Brady Christensen · Updated 2025 · 20 min read
15–40%
of traffic blocked from GA4 by ad blockers
7-day
Safari ITP cookie limit — kills return attribution
20–30%
typical session gap: client-side vs server-side GA4

01What Ad Blockers Actually Block

Ad blockers don't just hide ads. They intercept outbound HTTP requests at the network layer — before the browser even sends them. When a visitor lands on your site, your GA4 tag tries to fire a request to www.google-analytics.com/g/collect. An ad blocker sees that URL, matches it against a filter list, and kills the request before it leaves the browser.

The major blockers each work a bit differently. Understanding the difference matters for how you fix the problem.

uBlock Origin
Network filter — pattern matching
Blocks /g/collect endpoint (GA4 data)
Blocks gtag.js and analytics.js
Blocks www.googletagmanager.com
EasyPrivacy list targets all telemetry domains
Allows same-site first-party requests
Brave Browser
Built-in engine — default on
Blocks third-party trackers by default
Blocks cross-site cookies and fingerprinting
Restricts scripts to isolated, ephemeral context
Permits first-party scripts for basic functionality
No extension install required to trigger blocking
Privacy Badger (EFF)
Behavioral + list — inconsistent
Blocks domains observed tracking across multiple sites
Blocks GA4 if cross-site behavior is detected
Does not block GA4 consistently on all sites
Now uses predefined lists as well as heuristics
Lower blocking rate than uBlock in practice
Firefox ETP
Built-in — Disconnect list
Blocks known trackers from the Disconnect list
Strict Mode blocks GA4 scripts entirely
DNS API allows real-time CNAME uncloaking
Standard Mode allows GA4 with minor restrictions
Most users stay on Standard, not Strict

The key insight: all of these tools allow requests that look genuinely first-party. That is the mechanism server-side tracking exploits. More on that in section 04.

02How Safari ITP Kills Attribution

Safari's Intelligent Tracking Prevention (ITP) runs without any extension. It's built into every iPhone, iPad, and Mac. It doesn't block GA4 outright — it's more destructive than that. It lets the script fire, then deletes the cookies shortly after.

Here's the specific rule that breaks WooCommerce attribution: any first-party cookie set via JavaScript (document.cookie) gets a hard 7-day expiration. Your _ga cookie — the one that ties a user's session history together — gets wiped after one week of inactivity.

✗  Standard GA4 Setup — Safari Kills the Cookie
Your Google Ads campaign gets zero credit. The sale shows as direct traffic. ROAS looks artificially low — Google's bidding algorithms pull back spend.

✓  Server-Side Setup — Cookie Survives
Server-side containers write cookies via HTTP Set-Cookie headers. When hosted on a true first-party subdomain, Safari treats these as legitimate first-party cookies and allows up to 13 months.
Safari 26 Makes This Worse

Apple's Safari 26 (shipping mid-2025) adds Advanced Fingerprinting Protection. It restricts script access to canvas and audio APIs, and blocks marketing scripts from using local storage to create persistent identifiers. Client-side tracking on Safari is getting harder — not easier — over time.

03The Real Impact on Your Data

This is not a minor data quality issue. Missing 15–40% of your traffic distorts every downstream decision: which channels get budget, how Google's Smart Bidding allocates spend, and how confidently you can trust your attribution model.

What actually goes missing

  • Sessions and users. Blocked GA4 tags never fire. Those sessions don't exist in your reports — not as "unknown," just gone.
  • Conversion attribution. When the _ga cookie is deleted before a purchase, the transaction gets credited to Direct/None. The paid channel that drove the visit loses credit entirely.
  • Remarketing audience size. Your mid-funnel and cart-abandonment audiences rely on tracking behavior over 30 days. ITP's 7-day cookie wipes wipe most of these signals. Audiences shrink. Retargeting efficiency drops.
  • Smart Bidding accuracy. Google Ads Target ROAS and Target CPA strategies train on your conversion data. If conversions are under-reported or mis-attributed, the bidding model optimizes toward a false signal and performance degrades.

Does GA4's built-in modeling compensate?

GA4 includes Behavioral Modeling for Consent Mode. It estimates the behavior of users who blocked tracking, using machine learning trained on your consented users. This sounds helpful — but it has two hard limits.

First, it only activates if you consistently have at least 1,000 events per day with analytics storage denied and 1,000 daily users with analytics storage granted. Most mid-market WooCommerce stores don't hit this threshold reliably. Second, modeled data is a statistical estimate — it can't produce the individual-level attribution data needed for accurate ROAS, CRM identity resolution, or financial reconciliation.

Modeling is a fallback, not a fix.

Running the same problem on Meta? Facebook's Pixel is blocked by the same tools. The fix is Facebook's Conversions API — same architecture, different platform.

Meta CAPI Fix →

04How Server-Side GTM Bypasses Blockers

The root problem is simple: ad blockers block third-party requests. Your browser sending data to google-analytics.com is a third-party request. The fix is equally simple in concept: make the tracking request look first-party.

Server-side GTM (sGTM) does this by putting a relay server — your server, on your domain — between the browser and Google's servers.

// client-side tracking — blocked by most ad blockers
Browser
google-analytics.com/g/collect
BLOCKED by uBlock / Brave / ITP
vs server-side
// server-side tracking — first-party, not blocked
Step 1 — Browser sends to your own domain (first-party)
Browser
metrics.yourstore.com/g/collect
Passes blockers — same-site request
Step 2 — Your server relays to Google server-to-server
Your sGTM Container
Processes & transforms data
google-analytics.com
Ad blockers operate in the browser. Once the request reaches your server container, it's outside the browser — no extension can intercept it. The relay to Google happens server-to-server and is invisible to browser-level blockers.

The custom subdomain is not optional

Your sGTM container must run on a subdomain you own — like metrics.yourstore.com. This does two things. It makes the browser request first-party (same registrable domain), which most blockers allow. And it lets your server write cookies via HTTP headers instead of JavaScript, which extends cookie lifespan past Safari's 7-day JavaScript limit.

Firefox Can Still Block CNAME-Cloaked Subdomains

Firefox provides a browser.dns API that lets advanced blockers resolve DNS records in real time. If your subdomain's IP address resolves to a known Google Cloud Run or Cloudflare IP block, Firefox can detect the CNAME alias and block the request anyway. This is why true same-origin path routing (e.g., yourstore.com/metrics) is more resilient than a subdomain — but also significantly harder to configure.

05Hosting Options: Stape vs Google Cloud

Your sGTM container needs a server to run on. Two options dominate for WordPress and WooCommerce stores. The right choice depends on your team's DevOps capacity and traffic volume.

Google Cloud Run

Infinite scalability (serverless)
Cheapest at very high volume
Full infrastructure control
Complex DevOps setup required
Variable billing — spikes possible
No visual debugger built in
Up to €0.46/10k if misconfigured

Google App Engine

Native Google infrastructure
Standard deployment pipeline
Instance-based billing (not serverless)
Slower cold start times
Manual DNS and SSL binding required
Rarely used for sGTM in practice

For most WooCommerce stores — especially those without dedicated DevOps capacity — Stape is the faster and more cost-predictable choice. Google Cloud Run makes sense when you already have GCP infrastructure or need to handle enterprise-scale traffic with custom configuration.

A full comparison of costs and architecture tradeoffs is covered in the Stape vs Google Cloud for GTM guide →

06WordPress Implementation Steps

Setting up server-side GTM on WordPress involves four distinct layers: DNS, the GTM containers, your WordPress snippet, and your WooCommerce dataLayer. Each layer must work correctly for the others to function.

1

Provision the server container and set your DNS

Create a server container in GTM, then deploy it to Stape or GCP. Add a subdomain to your DNS — typically an A record or CNAME pointing metrics.yourdomain.com to your container's IP or hostname. SSL must be fully provisioned before any tracking can work. Missing SSL causes hard ERR_CONNECTION_CLOSED failures.

2

Update the GTM snippet in WordPress to load from your subdomain

Your standard GTM snippet loads the library from www.googletagmanager.com/gtm.js. Change the src to metrics.yourdomain.com/gtm.js. This makes even the initial script load first-party — so the library itself can't be blocked by hostname-based rules. Most stores manage this via GTM4WP plugin or hardcoded in header.php.

3

Set the server_container_url in your GA4 Configuration Tag

Inside the web GTM container, open your GA4 Configuration Tag. Add the server_container_url parameter and set it to your subdomain. This tells gtag.js to stop sending data to Google directly and route everything through your relay server instead.

4

Configure the GA4 Client in the server container

The server container needs a GA4 Client to recognize incoming requests. This client listens on the /g/collect path, parses the payload, and forwards it to Google Analytics. If this client isn't configured and published, every incoming request hits a "No Client Claimed the Request" error and is silently dropped.

5

Audit and disable conflicting plugins

Plugins like Google Site Kit and MonsterInsights inject their own GA4 configuration tags independently of GTM. If one of these is active, it will send data directly to google-analytics.com — bypassing your server container entirely. This creates duplicate events and leaves that data exposed to ad blockers. One analytics path only.

Critical WordPress-specific failure: Cloudflare Rocket Loader

Cloudflare's Rocket Loader feature rewrites <script> tags to type="text/rocketscript" to defer JavaScript execution. This breaks GTM initialization. The web container snippet never runs properly, which means no data reaches your server container — even though everything else is configured correctly. You must create a Cloudflare Page Rule to bypass Rocket Loader for your GTM script path.

gtm-snippet-modified.html HTML
<!-- Standard GTM snippet — BEFORE (loads from Google's servers) --> <script> (function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXX'); // loads from: www.googletagmanager.com/gtm.js (blockable) </script><!-- Server-side GTM snippet — AFTER (loads from YOUR subdomain) --> <script> (function(w,d,s,l,i){ w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'}); var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:''; j.async=true; // ↓ CHANGED: points to your subdomain, not Google j.src='https://metrics.yourstore.com/gtm.js?id='+i+dl; f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-XXXXXX'); </script>

Need this set up correctly? We handle the full sGTM build — DNS, container config, WooCommerce dataLayer, and ITP cookie restoration — as part of our tracking integrity service.

View Tracking Services →

07Edge Cases: Caching, Guests, and GDPR

Page caching breaks dynamic dataLayer variables

WP Rocket, Varnish, and Cloudflare page caching store static HTML snapshots of your pages. If your WooCommerce theme or a plugin generates dataLayer variables dynamically via PHP — like cart contents or user status — a cached page serves the stale PHP output. The dataLayer arrives empty or with wrong values. Your server container receives garbage data and forwards garbage to GA4.

The fix: configure your caching layer to exclude dynamic variables from caching, or use cookie-based exclusion rules for logged-in users and active cart sessions. Most WooCommerce stores need caching bypassed for any page with dynamic cart state.

Guest checkouts lose identity mid-funnel

Logged-in customers have a WordPress User ID. You can pass this as the GA4 user_id parameter, which enables cross-device attribution and long-term behavioral analysis. Guest checkouts don't have this — until after they complete the purchase.

The solution is session-based capture. When a visitor arrives from a tracked ad, immediately store their _ga client ID and any URL click parameters in a PHP session variable. When the guest submits their billing email during checkout, hash it (SHA-256) and use it as a stable fallback identifier. This bridges the anonymity gap and allows the server to stitch together the full funnel even without a WordPress user account.

GDPR does not go away with server-side tracking

A common misconception: moving tracking to the server removes GDPR obligations. It doesn't. Whether data is collected in a browser or on a server, collecting PII or setting persistent tracking identifiers requires a lawful basis — which, for advertising, means explicit consent in the EU.

What server-side does improve is your ability to enforce that consent. With client-side scripts, a third-party vendor (Meta, Google) loads directly in the browser and collects whatever ambient data it wants. With sGTM, all data flows through your server first. You can configure Transformation rules to strip IP addresses, redact sensitive parameters, and enforce SHA-256 hashing before anything leaves your infrastructure. You control what third parties receive.

Your CMP (Consent Management Platform) must gate both the browser pixel and the server-side trigger independently. If a user declines, the server container must not fire — not just the browser pixel.

08Validation: How to Confirm It's Working

The 20–30% session gap is normal — here's why

When you first compare your new server-side GA4 property to your legacy client-side one, you'll almost always see the server-side property reporting fewer sessions. This is confusing, because the whole point was to capture more data. The discrepancy is real — but it's not a failure. It's almost always a configuration gap, not an infrastructure gap.

The most common cause: the web container is not passing URL query parameters — gclid, fbclid, UTM parameters — to the server container. Without these, every session arrives at the server container without attribution context. GA4 still records the session, but the source/medium defaults to (not set) or direct/none. Reconcile by auditing what the web container sends in GTM Preview mode and ensuring all attribution parameters are explicitly packaged into the outbound payload.

Debugging tools

  • Browser Developer Tools → Network tab. Filter by your measurement ID. Requests to metrics.yourstore.com returning HTTP 200 means data is reaching your server. Requests to google-analytics.com returning blocked:other confirm the old client-side path is blocked and no longer active.
  • GTM Server Preview mode. Shows exactly what data arrives at the server container. Inspect every variable — missing client_id or session parameters here explains most attribution failures.
  • GA4 DebugView. Real-time event inspection for your GA4 property. Confirm events are flowing in under 30 seconds of firing.

What a healthy setup looks like

  • Network tab shows all /g/collect requests going to metrics.yourstore.com, not google-analytics.com
  • Server container Preview shows client_id, session_id, and UTM parameters all present in incoming payloads
  • GA4 Acquisition report shows channel distribution matching expectations — not inflated Direct/None
  • Session volume in server-side property within 5–10% of WooCommerce order count on a 48-hour delay
  • No secondary plugin silently injecting a duplicate client-side GA4 tag
Server-Side GTM Launch Checklist
Custom subdomain DNS resolves correctly and SSL is active (test at https://metrics.yourdomain.com/healthz)
GTM snippet in WordPress updated to load library from custom subdomain, not googletagmanager.com
GA4 Configuration Tag has server_container_url set to custom subdomain
GA4 Client is configured and published in server container (listens on /g/collect)
No secondary analytics plugins (Site Kit, MonsterInsights) injecting duplicate GA4 tags
Cloudflare Rocket Loader bypassed for GTM script path via Page Rule
UTM params, gclid, and fbclid explicitly passed from web container to server container
Caching layer excludes pages with dynamic cart or user state
CMP consent state gates server container execution — not just browser pixel
Network tab confirms zero requests to google-analytics.com from browser

Ready to stop losing data to ad blockers? We audit your current setup, identify exactly what's being dropped, and implement the full server-side architecture — GTM container, subdomain proxy, caching exclusions, and consent mode.

Get in Touch →