Cookies on the internet, the good, bad and ugly.

Contents
<p>In 2020 Google announced it would phase out third-party cookies in Chrome within two years. It&rsquo;s 2026, third-party cookies are still on by default, and in 2024 Google quietly abandoned the plan entirely — then in April 2025 confirmed it wouldn&rsquo;t even show users a choice prompt. That single broken promise tells you almost everything about how cookies actually work on the internet: the technology is trivial, the incentives are not, and the gap between &ldquo;we&rsquo;ll fix the privacy problem&rdquo; and &ldquo;we run an advertising business&rdquo; is where the whole mess lives.</p> <p>I&rsquo;ve spent more time than is healthy reading <code>Set-Cookie</code> headers, partly out of curiosity and partly because if you self-host anything with a login, you end up owning this problem yourself. So here&rsquo;s the honest version: what a cookie is, what it&rsquo;s good for, where it turns nasty, and how to set one that won&rsquo;t get you owned.</p> <h2 id="what-a-cookie-actually-is">What a cookie actually is</h2><div class="ad-unit ad-in-article" aria-label="Advertisement"> <span class="ad-label">Advertisement</span> <ins class="adsbygoogle" style="display:block;text-align:center" data-ad-client="ca-pub-3726833845844946" data-ad-slot="3291553914" data-ad-format="auto" data-full-width-responsive="true"></ins> <script>(adsbygoogle = window.adsbygoogle || []).push({});</script> </div> <p>A cookie is a small piece of text — a name, a value, and some attributes — that a server asks your browser to store and send back on future requests to the same site. That&rsquo;s it. There is no magic. The server sends a header:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">Set-Cookie: session=a1b2c3d4; Path=/; Max-Age=3600; Secure; HttpOnly; SameSite=Lax </span></span></span></code></pre></td></tr></table> </div> </div><p>and from then on your browser includes <code>Cookie: session=a1b2c3d4</code> on requests to that origin until it expires. HTTP is stateless — every request is an amnesiac that has never seen you before — so cookies are how a site remembers that the request that just logged in and the request that&rsquo;s now loading your dashboard are the same person. Without them you&rsquo;d re-authenticate on every single page load.</p> <p>The crucial distinction is <strong>first-party versus third-party</strong>. A first-party cookie is set by the site in your address bar. A third-party cookie is set by some <em>other</em> domain whose content is embedded on the page — an ad network, an analytics script, a social &ldquo;like&rdquo; button. Your browser will happily store a cookie for <code>adnetwork.example</code> while you&rsquo;re on <code>news.example</code>, and send it back the next time <code>adnetwork.example</code>&rsquo;s code appears on a completely different site. That cross-site memory is the entire mechanism behind tracking.</p> <h2 id="the-good">The good</h2> <p>Most cookies are boring and useful, and a web without them would be worse for everyone. Session cookies keep you logged in. They remember the items in your basket, your language, whether you&rsquo;ve dismissed a banner, your dark-mode preference. They let a site enforce CSRF protection by pinning a token to your session. None of this is sinister; it&rsquo;s the difference between a usable application and one that forgets you exist between clicks.</p> <p>For anyone running a service, cookies are also the cleanest place to put a session identifier — far better than stuffing it in a URL, where it leaks into logs, referrer headers and browser history. If you&rsquo;ve ever wrestled with auth in a self-hosted stack, you&rsquo;ve leaned on cookies whether you noticed or not. The good cookie is a tool that does one job for one site and goes home when it&rsquo;s done.</p> <h2 id="the-bad">The bad</h2><div class="ad-unit ad-in-article" aria-label="Advertisement"> <span class="ad-label">Advertisement</span> <ins class="adsbygoogle" style="display:block;text-align:center" data-ad-client="ca-pub-3726833845844946" data-ad-slot="3291553914" data-ad-format="auto" data-full-width-responsive="true"></ins> <script>(adsbygoogle = window.adsbygoogle || []).push({});</script> </div> <p>The bad cookie is the third-party tracker. The technique is simple: an ad network&rsquo;s script runs on thousands of sites, and each time it loads it reads its own cookie. That cookie is a stable identifier, so the network builds a profile — <code>user 7f3a visited a running-shoe shop, then a marathon blog, then a physiotherapy clinic</code> — and sells the ability to target that profile across the web. You never visited the ad network directly. You never agreed to anything you&rsquo;d recognise as agreement. The profile follows you because the same third-party code rides along on every page.</p> <p>It gets worse with <strong>cookie syncing</strong>, where ad networks swap identifiers with each other so a profile assembled by one company is readable by dozens. The data is rarely confined to cookies, either — it&rsquo;s joined with IP addresses, browser fingerprints, and whatever a data broker already holds. A cookie is just the convenient handle on a much larger file.</p> <p>This is the same fundamental tension I keep running into across the whole stack: a feature designed for legitimate state management gets repurposed for surveillance because the incentive is there. It&rsquo;s the cookie version of the recurring pattern in <a href="/story/javascript-the-good-bad-and-ugly/">JavaScript, the good, bad and ugly</a> — a genuinely useful primitive that the ecosystem bent toward something users never asked for.</p> <h2 id="the-ugly">The ugly</h2> <p>The ugly part isn&rsquo;t the tracking, it&rsquo;s the theatre built to launder it. The EU&rsquo;s GDPR (note: the older version of this post misspelled it; it&rsquo;s the General Data Protection Regulation) and the ePrivacy Directive require consent for non-essential cookies. The intended outcome was less tracking. The actual outcome, for most of the web, was the consent banner — a dark-patterned modal with a giant &ldquo;Accept All&rdquo; button and a &ldquo;Manage preferences&rdquo; link buried in grey text that leads to forty toggles defaulted to on. The law asked sites to get permission; the industry responded by making refusal as tedious as possible.</p> <p>And then there&rsquo;s the security failure mode. A cookie holding a session token is, functionally, a password. If an attacker steals it, they <em>are</em> you for the lifetime of that session — no login required. Cross-site scripting (XSS) that can read <code>document.cookie</code> hands those tokens straight over. A cookie sent over plain HTTP can be sniffed on any shared network. These aren&rsquo;t exotic attacks; they&rsquo;re the bread and butter of session hijacking, and the defences have existed for years. Plenty of sites still don&rsquo;t use them.</p> <h2 id="setting-a-cookie-that-wont-get-you-owned">Setting a cookie that won&rsquo;t get you owned</h2> <p>If you operate a service, the attributes matter more than anything else here. Get these right and most of the ugly goes away:</p> <div class="highlight"><div class="chroma"> <table class="lntable"><tr><td class="lntd"> <pre tabindex="0" class="chroma"><code><span class="lnt">1 </span></code></pre></td> <td class="lntd"> <pre tabindex="0" class="chroma"><code class="language-http" data-lang="http"><span class="line"><span class="cl"><span class="err">Set-Cookie: session=...; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400 </span></span></span></code></pre></td></tr></table> </div> </div><ul> <li><strong><code>HttpOnly</code></strong> — forbids JavaScript from reading the cookie via <code>document.cookie</code>. This is your primary defence against XSS stealing session tokens. There is almost never a good reason to omit it on a session cookie.</li> <li><strong><code>Secure</code></strong> — the cookie is only sent over HTTPS, so it can&rsquo;t be sniffed on the wire. Mandatory in practice; it&rsquo;s also <em>required</em> if you set <code>SameSite=None</code>.</li> <li><strong><code>SameSite</code></strong> — controls whether the cookie rides along on cross-site requests. <code>Strict</code> sends it only on same-site navigation; <code>Lax</code> (the modern default in Chromium since Chrome 80) allows it on top-level navigations but blocks it on cross-site sub-requests, which kills most CSRF and a lot of third-party tracking; <code>None</code> sends it everywhere but demands <code>Secure</code>. The key advice from every current guide: <strong>set it explicitly on every cookie</strong>, because relying on browser defaults gives you different behaviour across Chrome, Firefox and Safari and that inconsistency will bite you.</li> <li><strong><code>Max-Age</code> / <code>Expires</code></strong> — keep session lifetimes short. A stolen token that expires in an hour is far less valuable than one good for a year.</li> </ul> <p>Defaults vary by browser in ways worth knowing. Chromium treats a cookie with no <code>SameSite</code> as <code>Lax</code>. Firefox layers on Total Cookie Protection, partitioning third-party cookies per top-level site so the cross-site handle stops working. Safari&rsquo;s Intelligent Tracking Prevention blocks most third-party cookies outright, regardless of what you set — even <code>SameSite=None; Secure</code> can be dropped in a third-party context. The browsers are, slowly and unevenly, doing what Google promised and abandoned.</p> <p>Two more attributes are worth a mention because they close real gaps. <code>__Host-</code> and <code>__Secure-</code> cookie name prefixes let the browser enforce constraints the server might otherwise get wrong: a cookie named <code>__Host-session</code> is <em>only</em> accepted if it&rsquo;s <code>Secure</code>, has <code>Path=/</code>, and carries no <code>Domain</code> attribute, which neatly prevents a subdomain from setting a cookie that shadows the parent. And the newer <strong>CHIPS</strong> mechanism (<code>Partitioned</code>) lets a legitimate embedded service keep a cookie that&rsquo;s scoped to the embedding site only — useful state without the cross-site tracking, which is roughly the compromise the whole industry should have landed on years ago.</p> <p>It&rsquo;s also worth being honest about what cookie controls <em>don&rsquo;t</em> fix. Blocking third-party cookies does nothing against browser fingerprinting, where a site identifies you from your fonts, screen size, GPU and timezone without storing anything at all. Killing cookies pushes trackers toward techniques that are harder to see and harder to block, which is part of why the privacy fight is never &ldquo;won&rdquo; by one setting. Cookies are the visible, regulated handle on tracking; remove it and the data collection finds a less visible one.</p> <h2 id="a-few-troubleshooting-notes">A few troubleshooting notes</h2> <p>If your cookies aren&rsquo;t behaving, the cause is usually one of these:</p> <ul> <li><strong>Cookie set but never sent back</strong> — almost always a <code>Domain</code>/<code>Path</code> mismatch, or you set <code>Secure</code> and you&rsquo;re testing over plain HTTP. Open the browser dev-tools Application tab and look at the stored cookie&rsquo;s actual attributes, not what you think you sent.</li> <li><strong><code>SameSite=None</code> cookie silently dropped</strong> — you forgot <code>Secure</code>. Browsers reject <code>SameSite=None</code> without it, no warning beyond a console note.</li> <li><strong>Third-party cookie missing in Safari/Firefox but fine in Chrome</strong> — that&rsquo;s ITP or Total Cookie Protection partitioning it, not a bug. If your design depends on a third-party cookie working, your design is already on borrowed time.</li> <li><strong>Session &ldquo;randomly&rdquo; lost</strong> — check whether two backends are issuing cookies with different <code>Path</code> or <code>Domain</code> scopes and clobbering each other.</li> </ul> <p>Inspect the raw headers rather than trusting your framework&rsquo;s abstraction. <code>curl -v https://example.com</code> shows you the exact <code>Set-Cookie</code> line the server sent, which is frequently not what the config implied.</p> <h2 id="the-verdict">The verdict</h2> <p>Cookies are a double-edged tool, and pretending otherwise — in either direction — gets you nowhere. As session storage they&rsquo;re the cleanest mechanism we have, and a web without them would be a worse place to build and use. As cross-site trackers they power an industry most users would opt out of if refusal weren&rsquo;t deliberately exhausting. The technology isn&rsquo;t the villain; the incentives draped over it are.</p> <p>If you&rsquo;re a user: install a decent content blocker, use a browser that partitions third-party cookies by default, prune your stored cookies occasionally, and stop clicking &ldquo;Accept All&rdquo; out of fatigue — the two extra clicks to refuse are the price of not being the product. If you&rsquo;re a developer: set <code>Secure; HttpOnly; SameSite</code> on every cookie, keep sessions short, and never put anything in a cookie you&rsquo;d mind seeing leaked — because eventually something leaks. The same hard-nosed pragmatism applies here as everywhere else in this series; if you liked this teardown, the same &ldquo;good, bad and ugly&rdquo; treatment of <a href="/story/php-the-good-bad-and-ugly/">PHP</a> covers a tool with an even longer rap sheet. Cookies aren&rsquo;t going anywhere — Google made sure of that when it walked back a deprecation it had promised the world for four years — so the only sensible move is to stop treating them as a black box and understand them well enough to use the good parts deliberately and defend against the rest on purpose.</p>
Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.