Safari deletes your tracking cookies after 7 days. This puts them back — from your own server.
Zen Cookie Keeper is a free WordPress plugin that writes your analytics and advertising cookies with a server Set-Cookie instead of leaving them to JavaScript. The browser then grants them their full intended lifetime, so returning visitors are still recognised and your paid-ad results are still measured.
The industry solves this with server-side Google Tag Manager: a separate always-on container, a custom subdomain, an SSL certificate and a monthly bill — for what is architecturally a single HTTP header. This does that one header from inside WordPress.
No container. No subdomain. No monthly bill.
GPL v2 or later. Free, and free of any upsell.
Why we built it
We run paid campaigns for clients. We kept meeting the same quiet failure: the numbers in the ad platform and the numbers in analytics drifted apart, conversions arrived unattributed, and "Direct" traffic grew every quarter without anyone changing anything.
The cause is not a misconfigured tag. Safari's Intelligent Tracking Prevention caps any cookie written by JavaScript at 7 days — and at 24 hours when the visitor arrives from a link carrying ad-click parameters. Firefox's Enhanced Tracking Protection does much the same. Google Analytics, Google Ads, Meta, Microsoft, TikTok and LinkedIn all write their cookies from JavaScript, so all of them are capped.
The consequence is not subtle. A visitor who returns a week later is a brand-new person. A click that converts on day three has already lost the trail back to the ad that paid for it. You keep paying for the ads; you stop being able to tell which ones worked.
Every quote we got for fixing it properly was a server-side GTM build: infrastructure to stand up, a subdomain to certify, and a recurring cost — to change where one cookie is written from. WordPress is already a server answering every one of those requests. So we wrote the plugin instead, and now run it on our own site.
What the caps actually are
- 7 days — any cookie written by JavaScript, Safari ITP.
- 24 hours — the same cookie when the landing URL carries a click ID.
- Full lifetime — a cookie written by your server, which is what this plugin does.
How it works
Three mechanisms, chosen per cookie according to where its value comes from.
Mint
For ad-click cookies, the plugin builds the value on your server directly from the click parameter already in the landing URL — gclid, wbraid, gbraid, dclid, msclkid, fbclid, li_fat_id. Because your server sets it, the 24-hour link-decoration cap does not apply.
Capture
Some values can only be generated in the browser — the Google Analytics client ID, for one. A small read-only companion reads what your existing tag already wrote and reports it once. The plugin stores it and re-emits it from the server later.
Restore
On any later visit where the browser's copy has been dropped or has expired, the server sends the stored value back — up to the lifetime you set to match your privacy policy. This is the step that survives ITP.
Holding it together is one durable anchor cookie, set by the server, marked HttpOnly, Secure and SameSite=Lax. JavaScript can neither read it nor write it. It carries no marketing meaning of its own — it is only the key that ties a visitor's stored values and consent record together, and its lifetime is computed from the longest-lived cookie you have enabled.
Eight cookies ship preconfigured, across Google Analytics, Google Ads, Microsoft Ads, Meta, TikTok and LinkedIn. Default lifetimes are two years for _ga, 90 days for the advertising cookies and 30 days for li_fat_id — every one editable, and you can register your own cookies against either consent bucket.
Consent first, and nothing leaves your server
Gated per purpose
Analytics and advertising are gated separately, read from Google Consent Mode v2 — so it works with Cookiebot, Complianz, Borlabs and any other banner that supports Consent Mode. Nothing is set until the matching bucket is granted, and enforcement is on out of the box.
No outbound calls
The plugin connects to no external service whatsoever. There is no phone-home, no telemetry, no third-party request of any kind. It reads the incoming request and sets first-party cookies on your own domain. Nothing leaves your server.
Withdrawal and erasure
A consent record is kept per visitor. Withdrawing consent purges the anchor and everything stored against it. A daily cleanup enforces the retention window on every table, so nothing outlives the lifetime you declared.
What it does not do
So the scope is unambiguous before you install it.
It does not forward events to platforms. There is no Conversions API or Measurement Protocol here. It manages cookies, not event transport.
It does not proxy or disguise your analytics to get past ad blockers. That is a different job and a different argument.
It does not restore the GA4 session cookie (_ga_<id>). Its format is fragile to parse and restoring it is unreliable. Visitor identity — the _ga client ID — is what determines whether a returning person is recognised, and that is what gets restored.
It does not, by itself, fix bot inflation. Restoration grants a durable identity to every client equally, bots included. An optional module, off by default, can withhold that from clients flagged as bots.
Built for cached sites, and it shows its work
A page cache is the thing that quietly breaks most server-side cookie schemes: on a cache hit, PHP never runs, so nothing can set a header. Zen Cookie Keeper writes cookies through a POST endpoint your cache passes straight through to PHP, and marks the response non-cacheable — so it keeps working on fast, fully cached pages.
It recognises Varnish, LiteSpeed, WP Rocket, WP Super Cache, W3 Total Cache and Cloudflare, and a built-in self-test confirms the path on your own server rather than asking you to take it on faith.
The admin gives you six screens: an Overview with the privacy-policy line to copy, the cookie registry, consent settings, Restore History, Diagnostics, and Sites & domains for installs answering on more than one host.
Restore History
Every time the server puts a cookie back, it is recorded: which cookie, which consent bucket, and why — missing, meaning the browser had lost it, or divergent, meaning it held a different value. Each row also carries the age of the recovered identity, which is the plain evidence of ITP survival: a 300-day-old identity restored is 300 days Safari would have thrown away.
No cookie values are stored — only the event. Filter it, chart it, export it to CSV.
Installing it
About five minutes, and no infrastructure to provision.
- Download the zip and install it from Plugins → Add New → Upload Plugin, then activate it.
- Open Zen Cookie Keeper → Cookies, enable the cookies you actually use, and set each lifetime to match your privacy policy.
- Confirm your cookie banner has Google Consent Mode enabled — most do. The plugin reads consent from it automatically, with no extra wiring.
- Copy the one-line anchor-cookie note from the Overview screen into your privacy policy. It is the only cookie the plugin creates that your banner will not already describe.
- Open Diagnostics and run the self-test to confirm the write path on your server.
FAQ
The questions people ask before installing it.
No. The plugin makes no outbound third-party requests at all — there is no external service behind it and nothing to sign up for. It reads the incoming request and sets first-party cookies on your own domain. Nothing leaves your server.
Yes — it was written for exactly that case. The cookie write goes through a POST endpoint caches pass to PHP, and the response is marked non-cacheable. Varnish, LiteSpeed, WP Rocket, WP Super Cache, W3 Total Cache and Cloudflare are recognised, and the Diagnostics self-test confirms it on your own setup.
It solves the cookie-lifetime half of what sGTM is usually bought for, without the container, the subdomain or the bill. It does not do the other half: sGTM can also forward events server-to-server through the Conversions API or Measurement Protocol. If that is what you need, you need sGTM. If, like most sites, you bought it to stop ITP shredding your cookies, this does that part.
Both. The cookie domain is resolved from the request host, so the apex and www automatically share one set of cookies and a leading-dot domain covers subdomains. If one install answers on several distinct domains, set a per-host override on the Sites & domains screen. On multisite, each site in the network keeps its own tables, cookie catalog and settings.
We run it on this site
Zen Cookie Keeper is free and GPL-licensed, with no paid tier, no account and no upsell attached. We built it because we needed it, we run it on zenrepublic.agency, and the source is open if you would rather read it than trust it.