HTTP Cheatsheet
Cookies
Use this HTTP reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Setting and sending
The server sets one cookie per Set-Cookie header. The browser returns all
matching cookies in a single Cookie header, name and value only, with no
attributes.
HTTP/1.1 200 OK Set-Cookie: sid=a1b2c3; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600 Set-Cookie: theme=dark; Path=/; Max-Age=31536000; SameSite=Lax GET /dashboard HTTP/1.1 Cookie: sid=a1b2c3; theme=dark
Set-Cookie is the one header that is never folded into a comma-separated
list, because cookie values and dates contain commas.
Attribute reference
| Attribute | Value | Effect | Default when omitted |
|---|---|---|---|
Domain | example.com | also sent to subdomains | host-only, exact host |
Path | / | sent only for this path prefix | the current directory |
Expires | HTTP date | persistent until that time | session cookie |
Max-Age | seconds | persistent, wins over Expires | session cookie |
Secure | flag | HTTPS only | sent over plain HTTP too |
HttpOnly | flag | invisible to document.cookie | readable by JavaScript |
SameSite | Strict, Lax, None | cross-site sending rules | Lax in modern browsers |
Partitioned | flag | separate jar per top-level site (CHIPS) | shared third-party jar |
Priority | Low, Medium, High | eviction order, Chromium only | Medium |
Counterintuitive but important: omitting Domain is more restrictive than
setting it. No Domain means host-only, so example.com will not send it to
app.example.com. Setting Domain=example.com sends it to every subdomain.
There is no way to scope a cookie to a port, and Secure cookies set by
https://example.com can be read on any port of that host.
SameSite in detail
| Value | Same-site request | Cross-site top-level navigation | Cross-site subresource or XHR |
|---|---|---|---|
Strict | sent | not sent | not sent |
Lax | sent | sent, only for safe top-level GET | not sent |
None | sent | sent | sent, requires Secure |
- Lax is the browser default now, which is why a cross-site POST silently loses
its session cookie. That default is itself a large part of CSRF defense.
- Strict breaks the "click a link in an email and land logged in" flow, so it
suits banking and admin sessions, not general use.
- SameSite=None without Secure is rejected outright, and the cookie is
simply dropped.
- Third-party cookies are being restricted independently of SameSite, so
None alone no longer guarantees delivery. Partitioned (CHIPS) gives
embedded contexts a per-top-level-site jar instead.
Session vs persistent, and deletion
# session cookie, gone when the browser closes Set-Cookie: sid=a1b2c3; Path=/; Secure; HttpOnly; SameSite=Lax # persistent for one hour Set-Cookie: sid=a1b2c3; Path=/; Max-Age=3600; Secure; HttpOnly; SameSite=Lax # delete: same name, same Path and Domain, expired Set-Cookie: sid=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Lax Set-Cookie: sid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
Deletion only works when Path and Domain match the original exactly. A
mismatch leaves the old cookie in place and, worse, creates a second cookie with
the same name. Two cookies named sid on different paths are both sent, in an
order the spec does not fully pin down, and the server usually reads the wrong
one.
Browsers also ignore session-restore: "continue where you left off" restores session cookies, so they can outlive the window in practice.
Prefixes and hardening
| Prefix | Browser enforces |
|---|---|
__Secure- | must have Secure, must be set from a secure origin |
__Host- | must have Secure, must have Path=/, must have no Domain |
__Host- is the strongest option, because it pins the cookie to exactly one host
and no subdomain can overwrite it. That closes cookie tossing, where a
compromised or attacker-controlled subdomain sets a cookie on the parent domain
and shadows the real one.
Set-Cookie: __Host-sid=a1b2c3; Path=/; Secure; HttpOnly; SameSite=Lax
Session cookie checklist:
HttpOnly, so XSS cannot read the token.Secure, so it never crosses plaintext.SameSite=Laxat minimum,Strictfor admin surfaces.__Host-prefix, unless subdomains genuinely need it.- Short
Max-Ageplus server-side rotation on privilege change. - Rotate the session identifier on login, or session fixation stays open.
Limits and practical constraints
| Limit | Typical value |
|---|---|
| Size per cookie | 4096 bytes including name and attributes |
| Cookies per domain | ~180 in Chrome, 50 in older browsers |
| Total cookies | thousands, with LRU eviction |
| Request header total | servers commonly cap 8-16 KB, and 431 is the result |
Consequences you will hit: cookies are sent on every request to the matching host, including images and static assets, so a fat cookie jar taxes every request. Serving assets from a separate cookie-free domain used to be a standard fix, and it still helps.
Cookies are not the right place for application state. Use them for a session
identifier, put the data server side or in localStorage.
Reading and debugging
curl -sI https://example.com | grep -i '^set-cookie' curl -c jar.txt -s https://example.com/login -d 'u=a&p=b' # save cookies curl -b jar.txt -s https://example.com/dashboard # send them cat jar.txt # Netscape format curl -s -H 'cookie: sid=a1b2c3; theme=dark' https://example.com/api/me curl -v https://example.com 2>&1 | grep -iE 'cookie|set-cookie'
// browser: only non-HttpOnly cookies are visible here document.cookie // "theme=dark" (sid is HttpOnly) document.cookie = 'theme=light; path=/; max-age=31536000; samesite=lax' // the modern async API, readable and awaitable await cookieStore.get('theme') await cookieStore.set({ name: 'theme', value: 'light', path: '/' })
| Symptom | Cause |
|---|---|
| Cookie set but never sent back | Domain or Path mismatch, or Secure on plain HTTP |
| Works same-site, lost cross-site | SameSite=Lax default, needs None; Secure |
SameSite=None cookie dropped | missing Secure, or third-party cookies blocked |
| Cookie invisible to JS | HttpOnly, which is correct for sessions |
| Two values for one name | duplicate cookies on different Path or Domain scopes |
| 431 Request Header Fields Too Large | cookie jar overflowed the header limit |