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

AttributeValueEffectDefault when omitted
Domainexample.comalso sent to subdomainshost-only, exact host
Path/sent only for this path prefixthe current directory
ExpiresHTTP datepersistent until that timesession cookie
Max-Agesecondspersistent, wins over Expiressession cookie
SecureflagHTTPS onlysent over plain HTTP too
HttpOnlyflaginvisible to document.cookiereadable by JavaScript
SameSiteStrict, Lax, Nonecross-site sending rulesLax in modern browsers
Partitionedflagseparate jar per top-level site (CHIPS)shared third-party jar
PriorityLow, Medium, Higheviction order, Chromium onlyMedium

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

ValueSame-site requestCross-site top-level navigationCross-site subresource or XHR
Strictsentnot sentnot sent
Laxsentsent, only for safe top-level GETnot sent
Nonesentsentsent, 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

PrefixBrowser 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=Lax at minimum, Strict for admin surfaces.
  • __Host- prefix, unless subdomains genuinely need it.
  • Short Max-Age plus server-side rotation on privilege change.
  • Rotate the session identifier on login, or session fixation stays open.

Limits and practical constraints

LimitTypical value
Size per cookie4096 bytes including name and attributes
Cookies per domain~180 in Chrome, 50 in older browsers
Total cookiesthousands, with LRU eviction
Request header totalservers 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: '/' })
SymptomCause
Cookie set but never sent backDomain or Path mismatch, or Secure on plain HTTP
Works same-site, lost cross-siteSameSite=Lax default, needs None; Secure
SameSite=None cookie droppedmissing Secure, or third-party cookies blocked
Cookie invisible to JSHttpOnly, which is correct for sessions
Two values for one nameduplicate cookies on different Path or Domain scopes
431 Request Header Fields Too Largecookie jar overflowed the header limit