Safari's 7-Day Cookie Cap: Why Your Client-Side Sessions Keep Vanishing
Safari's ITP caps JavaScript-set cookies at 7 days, silently breaking analytics and sessions. Here's how the rule works, how to verify it, and why server-set cookies are the fix.
06 Nov 2025, 12:34 UTC

If your analytics show Safari users "returning" as brand-new visitors every week, the problem probably isn't your tracking code. It's Intelligent Tracking Prevention (ITP), WebKit's privacy engine, which caps the lifetime of any cookie set through document.cookie at seven days — and in some configurations, as little as 24 hours. The useful takeaway: where a cookie is set matters as much as what it contains, and moving cookie creation to your server is the standard fix.
What ITP actually does to cookies
ITP is Safari's on-device tracking defense. It classifies domains by behavior and then applies storage restrictions based on that classification. Two rules matter most for engineers:
- Third-party cookies are blocked entirely by default. Cross-site contexts get no cookie access unless the user explicitly grants it through the Storage Access API.
- First-party cookies set via JavaScript are capped at 7 days. If the user doesn't interact with your site within that window, the cookie is deleted. This applies to
document.cookiewrites regardless of theExpiresorMax-Ageyou specify.
The critical asymmetry: cookies delivered in an HTTP Set-Cookie response header are not subject to the same 7-day JavaScript cap. A server-set first-party cookie can persist for its declared lifetime (subject to other ITP heuristics), while the identical cookie written from a script evaporates in a week.
Why this breaks real systems
The classic casualty is client-side analytics and A/B testing. Tools that drop a visitor ID from JavaScript — which describes most tag-manager-based setups — silently lose identity continuity for Safari users. Consequences include inflated new-visitor counts, broken attribution windows, and experiment cohorts that reset mid-test.
Legacy session management is the second casualty. Any system that assumes a JavaScript-readable token will survive for weeks ("remember me" implemented in script, client-side feature flags keyed to a cookie) will behave differently for roughly a quarter of desktop users and the majority of iPhone users, since all iOS browsers use WebKit under the hood.
A worked example: server-set vs. script-set
Compare these two ways of setting the same visitor ID. First, the fragile version, run in page JavaScript:
// Runs in the browser — capped at 7 days by ITP
document.cookie = "vid=abc123; Max-Age=31536000; Path=/; SameSite=Lax";Despite the one-year Max-Age, Safari will discard this after 7 days without interaction. Now the server-side version — an HTTP response header from your own domain:
Set-Cookie: vid=abc123; Max-Age=31536000; Path=/; SameSite=Lax; HttpOnly; SecureThis cookie is set in a first-party HTTP response, so it keeps its declared lifetime. Note the HttpOnly flag: a side benefit of moving cookie writes server-side is that the ID no longer needs to be readable by script at all, which removes it as an XSS exfiltration target. If client code needs the value, expose it through an API endpoint instead of the cookie itself.
How to verify the behavior yourself
Don't take the 7-day figure on faith — ITP behavior shifts between WebKit releases, and macOS and iOS Safari differ. A practical check:
- In Safari, open Web Inspector (Develop menu; enable it in Settings → Advanced if hidden).
- Set a cookie via the console using
document.cookiewith a longMax-Age. - Open the Storage tab and inspect the cookie's actual expiration date. If ITP's cap applies, the expiry shown will be roughly 7 days out, not your declared date.
- Repeat with a cookie set via an HTTP
Set-Cookieheader from your server and compare expirations.
For a longer-term check, set both cookie types, avoid visiting the site for 8 days, then inspect storage again. The script-set cookie should be gone; the server-set one should remain. You can also accelerate testing by manipulating the system clock in a VM, though results there are approximate.
Trade-offs and limitations
Moving cookie creation server-side isn't free. It requires every page that might establish identity to be served (or at least cookied) by your infrastructure — a problem for statically hosted sites that previously relied on a third-party script. It also means your analytics vendor's out-of-the-box snippet may no longer suffice; many teams end up proxying vendor endpoints through their own domain or adopting server-side tagging.
There are also false positives. ITP's classification is behavioral, not semantic, so a legitimate functional cookie can be caught if your domain's request patterns look tracker-like — for example, if you're decorated in cross-site redirects with query parameters. Safari has also applied stricter caps (down to 24 hours) for cookies set via link decoration from known tracking domains. If your login flows bounce through multiple domains, test them in Safari specifically.
What to do this week
Audit every place your stack writes cookies from JavaScript. For each, ask whether it needs to survive longer than 7 days for Safari users. If yes, move the write to a Set-Cookie header from your own domain, mark it HttpOnly where possible, and verify the expiration in Web Inspector. That single change closes the most common ITP gap without touching your tracking logic at all.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.