Server-side Tracking already moves your data collection off the browser and onto your own server container, and TAGGRS's tracking infrastructure is resistant to ad blockers out of the box thanks to the Enhanced Tracking Script (ETS v3). The bigger reason to load everything from the same origin is cookie lifespan on iOS. Since Safari 16.4 in April 2023, Safari caps your server-set first-party cookies at 7 days unless the host that sets the cookie shares an IP range (specifically, the first half of the IP address) with your main website. A separate tracking subdomain like sst.yourdomains.com points at different infrastructure (your TAGGRS container), so the IP ranges don't match and Safari applies the cap. As a result, your 1- or 2-year identity cookie becomes a 7-day one, and any iOS visitor who doesn't return within the week is counted as new. With same-origin, every resource goes through Cloudflare and resolves to the same IP range, so the check passes and cookies keep their full lifespan. Because all iOS browsers run on Safari's engine, this applies to your entire iOS audience.
You can address this by loading everything from a path on your primary domain. For example, instead of metrics.yourdomain.com you serve tracking from yourdomain.com/metrics. There is no separate tracking hostname for browsers or blockers to recognize, so the request looks like any other first-party asset on your site. In practice, it can help recover a portion of traffic lost to hostname-based blocking and keep cookies in a first-party context.
This guide uses a Cloudflare Worker as a lightweight reverse proxy. You pick one path segment, such as /metrics. The Worker forwards everything under that path to your TAGGRS tracking host and strips the path, so TAGGRS still sees its normal URLs. The tracking script reads its own address in the browser and sends events and assets under the same path.
You'll need:
Set aside about 15 minutes. The path mapping lives in Cloudflare. You do not change Product ID routing inside the TAGGRS dashboard.
The examples below use /metrics but in your implementation avoid obvious tracking paths such as /sst or /data. Ad blockers often match those names, so pick something unique to your site.
Replace it with your path everywhere: the Worker route, the snippet, and, if applicable, the WAF rule.
Log in to the Cloudflare dashboard.
Go to Workers & Pages → Create application → Create Worker.
Choose the Hello World starter template.
Note: Do not pick Upload and deploy or Import a repository. Those expect a build pipeline and will reject a plain script.
Click Deploy to create the Worker with its sample code.
Once it's live, click Edit code.
Select all the sample code in the editor and delete it.Paste in the following code:
export default {
async fetch(request, env) {
if (!env.TAGGRS_HOST) {
return new Response('Worker misconfigured: set TAGGRS_HOST.',
{ status: 500 });
}
const url = new URL(request.url);
const target = new URL(url.pathname.replace(/^\/[^/]+/, '') + url.search, `https://${env.TAGGRS_HOST}`);
const headers = new Headers(request.headers);
headers.set('host', env.TAGGRS_HOST);
headers.set('x-forwarded-proto', 'https');
return fetch(target, {
method: request.method,
headers,
body: ['GET', 'HEAD'].includes(request.method) ? undefined : request.body,
redirect: 'manual',
});
},};This Worker reads your tracking host from one environment variable. It strips the first path segment and forwards the rest, including the query string. A request to yourdomain.com/metrics/abc1234567.js becomes /abc1234567.js on your tracking host.
You do not need to edit Product ID or path values inside this code. The host comes from the variable in Step 2. The path comes from the route in Step 3.
Click Deploy again to publish your version.
Open your Worker → Settings → Variables and Secrets → Add variable, and add one variable:
Save. If Cloudflare asks you to redeploy for the variable to apply, do so.
Open your Worker's Domains / Triggers tab and add a route for your domain.
Important: do not use yourdomain.com/*. That runs the Worker on every request to your site and counts all of them against Cloudflare Workers limits. Bind only your tracking path.
Add this route, replacing metrics with your path:yourdomain.com/metrics/*
If your site also runs on www, add the same pattern for www:www.yourdomain.com/metrics/*
The route field accepts one pattern at a time. Click Add route after each one.
Go to DNS → Records and confirm the records that serve your site show the orange cloud (Proxied).
Workers only run on proxied traffic. If your site is served from www but the www record is DNS only (grey cloud), the Worker will not apply there.
Because this setup requires the Cloudflare proxy (orange cloud), Cloudflare security features can block GTM Preview. Preview reloads your tracking script with gtm_debug, gtm_preview, and gtm_auth on the query string. Bot Fight Mode and the WAF often treat those requests as automated traffic, so Preview fails even though live tracking works.
The solution to this is to add a custom WAF rule that skips bot checks for Preview requests under your tracking path.
Go to Security → Security rules (or Security → WAF → Custom rules) → Create rule.
Name it Allow TAGGRS GTM Preview.
Use this expression, replacing metrics with your path:
(starts_with(http.request.uri.path, "/metrics/")) and (http.request.uri.query contains "gtm_debug" or
http.request.uri.query contains "gtm_preview" or
http.request.uri.query contains "gtm_auth")Set the action to Skip.
In the skip list, select Super Bot Fight Mode. If Bot Fight Mode appears as an option, select that too.
Deploy the rule.
On Cloudflare's Free plan, Bot Fight Mode cannot be skipped with a WAF rule. If Preview still fails on Free, turn Bot Fight Mode off for the zone or upgrade.
If collect calls or the loader itself return 403 with a cf-mitigated header, skip Super Bot Fight Mode for the whole /metrics/ path instead of only the Preview query parameters.
Keep the standard TAGGRS snippet. Change only the script and noscript paths so they include your tracking path and keep the Product ID filename.
Before:
j.src = 'https://sst.taggrs.io/' + i + '.js';After:
j.src = '/metrics/' + i + '.js';If you use the noscript fallback in the body, update it the same way:
Before:
<iframe src="https://sst.taggrs.io/abc1234567.html" ...></iframe>After:
<iframe src="/metrics/abc1234567.html" ...></iframe>The filename must stay {Product ID}.js. If you rename the file, the script cannot derive the public path and tracking falls back to the old tracking host.
You do not add a snippet variable. The loader reads its own script URL and sends events to /metrics/{Product ID} and assets to /metrics/dfp.min.js, /metrics/ns.html, and the rest.
Run through these four checks in order:1. Loader responds. Open https://yourdomain.com/metrics/YOUR_PRODUCT_ID.js. You should get HTTP 200 and JavaScript, not a 404.
2. Script loads first-party. Open your site, open DevTools → Network, reload, and confirm the tracking script loads from your domain under /metrics/{Product ID}.js.
3. Collect calls stay on the same path. Confirm collect traffic goes to /metrics/{Product ID} on your domain, not to the old tracking subdomain.
4. Data arrives. Check GA4 Realtime or your analytics dashboard and confirm events after the switch.
5. Cookie lifespan is fixed (iOS). In Safari, open your site, then DevTools → Storage, and find your tracking identity cookie. Confirm its expiry shows the full intended lifespan (often a year or more) instead of roughly 7 days.
6. Preview works. Open GTM Preview as described in Test your Server-side Tracking configuration. If Preview is blocked, go back to Step 6.
For most sites, this can run on Cloudflare's free Workers plan. The free tier allows 100,000 Worker requests per day, resetting at midnight UTC.
Because the Worker is bound only to your tracking path, normal pages, images, and CSS do not count. What counts is each TAGGRS request under that path: the loader, a few supporting assets, and each collect or tag call.
If you approach the free daily limit, Cloudflare's paid Workers plan removes the hard daily cap. Check current figures on Cloudflare's Workers pricing page.
Do I need to change anything in my TAGGRS dashboard?
No change is required for the path mapping itself. Your product, container, and tags stay as they are. If you use the Service Worker experiment, add your website hostname as a custom or additional domain so those requests resolve to the right product.
Will this affect my normal website pages?
No. The Worker only runs on the route you add, for example yourdomain.com/metrics/*. Everything else goes to your site as before.
Does this work if my site is not on Cloudflare?
This guide is for Cloudflare. The same strip-forward pattern works on other reverse proxies (Nginx, Vercel rewrites, edge functions). Use the same public path and the same snippet. If you are not using a reverse proxy, the standard domain routing setup via a tracking subdomain remains fully supported.
I already proxy these paths on my own server. Should I do both?
No. Pick one method only. Running the Cloudflare Worker and another proxy for the same path can cause double-forwarding.
Will this recover 100% of blocked tracking?
No setup can promise that. Same-origin loading removes hostname-based blocking, which is a common source of loss, but some browsers apply additional tracking-prevention. If you want to push measurability further, combine this with the Enhanced Tracking Script.
How do I change the path later?
Change the Worker route, the snippet src, and the WAF expression. Nothing else. The loader re-derives the new path from its own URL on the next page load.