skip to content

What is the difference between an HTTP cookie set without a Domain attribute and one set with `Domain=example.com`, and which hosts will the browser send each one to?

level: juniorimportance: must knowfreq 60%

answer

  1. no Domain = host-only, exact host only
  2. Domain= widens to all subdomains, never narrows
  3. leading dot is legacy, stripped
  4. must domain-match; public suffix blocked
  5. port ignored, cookies are not origin-scoped

basics

~10 s

No Domain means host-only: sent only to the exact host that set it. Domain=example.com means sent to example.com and every subdomain of it. Counter-intuitively, omitting Domain is the narrower, safer scope.

solid answer

~50 s

Omitting `Domain` creates a **host-only** cookie: the browser records the exact host from the response (`app.example.com`) and sends the cookie only to that host — not to `example.com`, not to `api.example.com`. Setting `Domain=example.com` creates a **domain cookie**: the browser sends it to `example.com` and to every subdomain, at any depth (`api.example.com`, `a.b.example.com`). Two rules trip people up: 1. A leading dot (`Domain=.example.com`) is legacy syntax; modern browsers strip it, so `.example.com` and `example.com` behave identically — both include subdomains. 2. You can only widen to a domain that **domain-matches** the setting host, and never past the public suffix. `app.example.com` may set `Domain=example.com`, but not `Domain=other.com` and not `Domain=com`. Default to host-only. Use `Domain=` only when you genuinely need the cookie across sibling hosts — and understand that every subdomain then reads and can overwrite it.

code

http · 7 lines
http
HTTP/1.1 200 OK
Set-Cookie: hostonly=1; Path=/
Set-Cookie: shared=2; Path=/; Domain=example.com

GET / HTTP/1.1
Host: api.example.com
Cookie: shared=2

go deeper

for a junior

State the rule plainly: no Domain means only the exact host; Domain=example.com means example.com and all subdomains.

for a middle

Add the domain-match restriction on which values are legal, the stripped leading dot, and that port and scheme do not scope cookies.

for a senior

Discuss host-only as the default, the trust boundary a shared apex cookie creates, and the redirect-based alternative for multi-host sessions.

for a principal

Frame subdomain policy for the whole estate — who may host under the apex, whether the session lives on the apex at all, and how that decision constrains acquisitions and third-party-hosted subdomains.

## The two scopes Every cookie in the browser's jar carries a domain and a `host-only` flag, decided when it was stored. **Host-only** (no `Domain` attribute in `Set-Cookie`): domain = the exact host of the response, host-only flag = true. The cookie is sent only when the request host equals that value, character for character. **Domain cookie** (`Domain=example.com` present): domain = `example.com`, host-only flag = false. The cookie is sent whenever the request host *domain-matches* it — that is, when the host is identical to `example.com` or is a subdomain ending in `.example.com`. So `Set-Cookie: a=1` from `app.example.com` goes only to `app.example.com`. `Set-Cookie: b=2; Domain=example.com` from the same response goes to `example.com`, `app.example.com`, `api.example.com`, `deep.nested.example.com` — all of them. The counter-intuitive part is that adding `Domain` *widens* scope. Many people write `Domain=app.example.com` on a response from `app.example.com` thinking it pins the cookie tighter; in fact it converts a host-only cookie into a domain cookie for `app.example.com`, which now also matches `x.app.example.com`. Slightly wider, never narrower. ## Which domains you are allowed to name The browser rejects the cookie unless the request host domain-matches the `Domain` value: - `app.example.com` may set `Domain=example.com` or `Domain=app.example.com`. ✓ - `app.example.com` may **not** set `Domain=other.com`, `Domain=example.org`, or `Domain=sub.app.example.com` (you cannot set a cookie for a host below you). ✗ - Nobody may set a cookie on a public suffix like `com`, `co.uk` or `github.io` — the browser consults the Public Suffix List to stop `evil.github.io` from writing a cookie visible to every other `*.github.io` site. The leading dot is a fossil from Netscape-era cookies where `.example.com` meant "include subdomains" and `example.com` meant host-only. RFC 6265 removed the distinction: the browser strips a leading dot, and a `Domain` attribute always means subdomains are included. Never rely on the dot for anything. ## Cross-site is impossible, by design There is no way for `example.com` to set a cookie that `other-site.com` will send. Cookies are keyed by domain, and the domain-match rule forbids naming an unrelated host. This is the whole reason third-party tracking historically used *third-party cookies* — cookies set by a resource loaded from the tracker's own domain, embedded in many sites — rather than any cross-domain sharing primitive, and why cross-domain SSO needs a redirect through the identity provider's own origin rather than a shared cookie. Note also that cookie scope ignores **port** and largely ignores **scheme**. `http://example.com:8080` and `https://example.com:3000` share one cookie jar entry, so cookies are *not* origin-scoped the way `localStorage` is. Two apps on different ports of `localhost` will stomp on each other's cookies. ## Choosing Default to **host-only**. It is the smallest blast radius, it is what the `__Host-` name prefix requires, and it means an incident on a marketing subdomain cannot touch the app's session. Reach for `Domain=example.com` only for a real requirement — typically a single sign-on session shared by `app.`, `admin.` and `api.` on the same registrable domain. Understand the cost: every present and future subdomain can read the cookie (unless `HttpOnly` keeps it out of scripts, in which case any subdomain page can still cause it to be sent) and can overwrite it, since a subdomain may set `Domain=example.com` too. That makes every subdomain — including the abandoned one, the customer-controlled one, and the third-party-hosted status page — part of your authentication trust boundary. A practical middle ground for multi-host products is to keep host-only session cookies per app and mint them through a redirect to a central auth host, rather than sharing one apex cookie everywhere.

  • A developer writes `Domain=app.example.com` on a response from app.example.com, believing it restricts the cookie to that host. What actually happens?
    It makes the cookie a domain cookie for app.example.com instead of a host-only cookie, so it is now also sent to any deeper subdomain such as beta.app.example.com. The intent was to narrow the scope, but the attribute can only widen it. Omitting Domain entirely is the way to get host-only behaviour.
  • What is the security cost of sharing one session cookie with Domain=example.com across all your subdomains?
    Every subdomain — current, future, abandoned, or operated by a third party — becomes part of the session's trust boundary. Any of them can cause the cookie to be sent, and any of them can set a same-named Domain=example.com cookie to shadow the real one. A single weak subdomain then compromises the whole product's authentication.

Host-only is mail addressed to one apartment. Domain= is mail addressed to the building — every apartment in it gets to open it, including the ones you have not rented out yet.

saying these in an interview costs you the question

  • Believing Domain= narrows the cookie's scope
  • Thinking a leading dot in Domain=.example.com still means something different
  • Claiming you can set a cookie for a different registrable domain to share it cross-site
  • Assuming cookies are scoped by port or by origin like localStorage
  • Forgetting that any subdomain can overwrite a Domain= cookie

context