LW IT Solutions
« Blog Overview /Digital Marketing / First-Party Cookie Restoration: Surviving ITP with Cloudflare...
Read this article in other languages:

First-Party Cookie Restoration: Surviving ITP with Cloudflare Workers and ssGTM

First-Party Cookie Restoration: Surviving ITP with Cloudflare Workers and ssGTM
Contents
  1. First-Party Cookie Restoration: Surviving ITP with Cloudflare Workers and ssGTM
  2. 1. The Architectural Blueprint: Server-Managed Edge Cookies
  3. 2. Cookie Restoration via Cloudflare Workers and ssGTM
  4. 3. Google Tag Gateway (GTG): The Perfect Edge Partner
  5. Summary
  6. Extending the Google Tag’s Own _ga Cookie Instead
  7. Questions and answers
  8. Sources

First-Party Cookie Restoration: Surviving ITP with Cloudflare Workers and ssGTM

Safari’s Intelligent Tracking Prevention (ITP) and modern browser privacy engines severely restrict client-side cookie persistence. Identifiers set via JavaScript (using document.cookie) are capped at a maximum lifespan of 7 days—and frequently reduced to 24 hours when ad-click decoration parameters are detected in the URL. Even traditional CNAME cloaking setups are identified and mitigated by WebKit CNAME defense algorithms when DNS records resolve to third-party tracking providers.

To preserve data fidelity and attribution accuracy over longer conversion cycles, data architectures must move away from JavaScript-managed storage toward True First-Party Edge Routing using server-managed HTTP cookies.

1. The Architectural Blueprint: Server-Managed Edge Cookies

Bypassing ITP’s 7-day client-side cap requires generating identifiers directly at the network edge via HTTP response headers (Set-Cookie). Because browser restrictions differentiate between client-script cookies and legitimate server-generated session headers, cookies configured via Edge Workers can remain intact for up to 365 days when compliant with regional consent regulations, provided the response comes from the site’s own infrastructure: if the responding host is reached through a third-party CNAME or a third-party IP address, WebKit caps cookies set in the HTTP response at 7 days as well.

Cookie AttributeRequired ValueTechnical Justification
HttpOnlytruePrevents JavaScript access, immunizing the cookie against ITP script caps and XSS theft.
SecuretrueMandates HTTPS transmission across all endpoints.
SameSiteLax / StrictEnsures the cookie is treated as a native first-party context.
DomainApex Domain (e.g., example.com)Prevents CNAME mismatch penalties across subdomains.
Three parts: the ITP limit with script cookies capped at 7 days against a Set-Cookie header from the edge lasting up to 365 days; the flow from browser through the Cloudflare Worker, which sets fp_id only when missing, to the ssGTM container, plus Google Tag Gateway; and four conditions for renewing _ga.
How a Cloudflare Worker restores a server-set first-party identifier for ssGTM, and what the narrower _ga renewal has to respect.

2. Cookie Restoration via Cloudflare Workers and ssGTM

An enterprise-grade cookie restoration pipeline leverages Cloudflare Workers as a reverse proxy in front of a Server-Side Google Tag Manager (ssGTM) container. When an HTTP request is intercepted at the edge, the Worker evaluates whether a valid, server-managed identifier exists. If it is missing, the Worker injects a new Set-Cookie header into the response.

// Cloudflare Worker Edge Script: Server-Managed Identifier Restoration
function getCookieValue(cookieHeader, name) {
  if (!cookieHeader) return null;
  for (const part of cookieHeader.split("; ")) {
    const eq = part.indexOf("=");
    if (eq !== -1 && part.slice(0, eq) === name) return part.slice(eq + 1);
  }
  return null;
}

export default {
  async fetch(request, env) {
    const response = await fetch(request);
    const newResponse = new Response(response.body, response);
    
    // Read or generate anonymous first-party identifier
    let fpId = getCookieValue(request.headers.get("Cookie"), "fp_id");
    if (!fpId) {
      fpId = crypto.randomUUID();

      // Set 1-year Server-Managed Set-Cookie header only when missing
      newResponse.headers.append(
        "Set-Cookie",
        `fp_id=${fpId}; Max-Age=31536000; Path=/; Domain=example.com; Secure; HttpOnly; SameSite=Lax`
      );
    }

    return newResponse;
  }
};

Within ssGTM, this persistent edge identifier is mapped to the standard GA4 or Google Ads client ID, stabilizing multi-touch attribution reports across extended customer journeys.

3. Google Tag Gateway (GTG): The Perfect Edge Partner

The Google Tag Gateway (GTG) represents a specialized first-party routing mechanism that tunnels analytics traffic directly through the primary apex domain. Deploying GTG via Cloudflare Workers creates an optimal synergy with edge cookie restoration:

  • Same-Origin Context: GTG routes script libraries (e.g., /gtg/gtm.js) and data payloads (/gtg/collect) through the identical IP address and Autonomous System Number (ASN) as the main web application.
  • Immunity to DNS CNAME Defense: Because GTG operates directly on the primary domain infrastructure via Edge Proxying rather than external DNS delegation, WebKit anti-tracking algorithms classify the traffic as native backend communication.
  • No Separate Tracking Connection: Executing GTG routing and cookie restoration within the same Cloudflare Edge Worker keeps the requests on the site’s own hostname, so the browser can reuse its existing connection instead of opening a TLS handshake to a separate tracking domain; the Worker’s onward request to the server container still adds a network hop.

Summary

Overcoming ITP restrictions requires shifting identifier management from browser scripts to edge infrastructure. Combining server-managed HttpOnly cookies via Cloudflare Workers with the same-origin routing of Google Tag Gateway (GTG) delivers a compliant, privacy-centric, and technically resilient measurement architecture.

Extending the Google Tag’s Own _ga Cookie Instead

The Worker above creates a separate identifier, fp_id, which the server container maps to the client ID. A narrower variant leaves the identifier to the Google tag and only renews the existing _ga cookie through an HTTP response, for example from a dedicated endpoint on the same hostname. Four details decide whether that works.

  • No HttpOnly on _ga: the Google tag reads the client ID via JavaScript, and an HttpOnly cookie is not accessible through document.cookie. The attribute table above applies to a separate server identifier, not to _ga.
  • A matching Domain: a new cookie replaces an existing one only if name, domain and path are identical. With cookie_domain: 'auto', the Google tag writes its cookies to the highest possible domain, so for www.example.com to example.com. A Worker that writes _ga with Domain=www.example.com creates a second cookie instead of renewing the first.
  • cookie_update: false in the Google tag: with the default true, the tag may update its cookies on every page load and reset the expiration relative to the latest visit, which in Safari again means a script-written cookie. With false, cookies are not updated on each page load.
  • Renew, never create: if the request carries no _ga, the endpoint answers without Set-Cookie. Otherwise the Worker and the tag each assign an ID on a first visit, and one visitor can end up as two users.

Google documents a default expiration of two years for _ga, which corresponds to Max-Age=63072000. Chrome has capped cookie expiration at 400 days since version 104 (August 2022): longer values are not rejected but shortened to 400 days, and every renewal on a later visit starts the period again.

The route through a response header does not change the consent requirement. Article 5(3) of the ePrivacy Directive covers the storing of and access to information on the device regardless of the technique, so the call to the renewal endpoint belongs behind a granted analytics_storage.

Questions and answers

Should the Worker write the cookie on every response, or only where the identifier is missing, when pages are cached at the edge?

Only where it is missing or close to expiry. The snippet in the article shows the mechanism, not the placement, and the placement is the part that decides whether the site stays cacheable.

A response carrying Set-Cookie should not be stored in a shared cache, and many shared caches refuse to store one by default. That is a correctness rule, not a tuning option, because a cached copy would hand one visitor’s identifier to the next. Depending on where in the chain the header is added, attaching it to every HTML response can therefore remove edge caching for the whole site, and the cost of that dwarfs anything the measurement setup gains.

Two adjustments keep both. The header is written only on the path that already has to be personal — the analytics endpoint the tag calls, not the page — and only when the cookie is absent or inside its renewal window. What arrives in the container is unchanged, because the identifier travels on the request either way. The cache status header on a page response shows the current state: if it reads as a miss on every request, the unconditionally written cookie is the first suspect.

How is an HttpOnly cookie such as fp_id with a one-year lifetime removed again when consent is withdrawn?

By the same route through which it was created: what the server sets, the server also has to take back. That is the obligation this architecture brings with it, and it is missing from many descriptions.

In practice it is a second path on the same Worker that overwrites the cookie with Max-Age=0, called from the withdrawal path of the consent tool. Without this path the identifier remains for a year although its basis has fallen away, and there is no evidence that it was removed either. The setting path matters just as much: the Worker in the article writes the cookie as soon as it is missing. If it does not check consent there, the very next request after deletion creates a new identifier.

One detail decides between success and appearance: the deletion cookie must carry the same name, the same domain and the same path as the original, in the example Domain=example.com and Path=/. If the domain or path differs, the instruction goes nowhere and the old cookie stays. The Worker’s response still looks correct, because the Set-Cookie header is present; only a look at the cookies actually stored shows that fp_id is still there.

Does a cookie on the root domain, sent with every request including those for static assets, break CDN caching?

It does, if the cache key includes cookies, and many configurations do so by default so as not to serve a logged-in user’s content to someone else.

The effect is silent and expensive: every visitor has a different identifier value, so every visitor gets a separate copy in the cache. The hit ratio drops almost to zero, the load on the origin server grows, and the site becomes slower without a single error message.

The fix is to exclude this particular cookie from the cache key; with most providers that is a single field in the configuration. It is worth doing deliberately and writing it down, because the rule looks dangerous (“ignore cookie”) but is in fact safe: an analytics identifier does not change the page content, so there is no reason for it to split the cache into as many parts as there are visitors.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Write a comment

Practical experience and questions about the implementation are welcome here.

The email address is not published. Required fields are marked with an asterisk.

Articles & categories

CCTV

Follow this category by RSS

Cloud & AI

All 11 articles in this category Follow this category by RSS

Data Privacy

All 18 articles in this category Follow this category by RSS

Digital Analytics

All 58 articles in this category Follow this category by RSS

Digital Marketing

All 37 articles in this category Follow this category by RSS

IT & Networks

All 17 articles in this category Follow this category by RSS

Music Production

All 16 articles in this category Follow this category by RSS

Raspberry PI

Follow this category by RSS

SaaS & Internet Earning

Articles coming soon

Smart Home

All 18 articles in this category Follow this category by RSS

Web Development

All 11 articles in this category Follow this category by RSS

WordPress Plugins & Tricks

All 13 articles in this category Follow this category by RSS