tallge

Reference / HTTP headers

HTTP headers: caching, cookies, CORS, security

Header names are case-insensitive; HTTP/2 and HTTP/3 send them in lowercase. The meanings below follow RFC 9110 (semantics), RFC 9111 (caching), RFC 6265 (cookies) with its revision draft 6265bis for SameSite and the cookie prefixes, and the Fetch standard (CORS).

Caching

HeaderExampleMeaning
Cache-Controlmax-age=3600the response is fresh for 3600 seconds; after that it must be revalidated or fetched again
Cache-Controlno-cachemay be stored, but must be revalidated with the origin before every use
Cache-Controlno-storemust not be stored by any cache at all
Cache-Controlprivateonly the browser’s own cache may store it, not a shared cache
Cache-Controlpublicany cache may store it, even when it would otherwise not be cacheable, such as a response to a request with credentials
Cache-Controls-maxage=600overrides max-age for shared caches only
Cache-Controlmust-revalidateonce stale, the response may not be used without a successful revalidation
Cache-Controlimmutablethe content will not change while fresh, so browsers that support it skip revalidation even on reload
Cache-Controlstale-while-revalidate=60a stale copy may be served for 60 seconds while a fresh one is fetched in the background (RFC 5861)
ExpiresWed, 07 Oct 2026 08:00:00 GMTolder absolute expiry; ignored when max-age is present
ETag"v42" or W/"v42"a version identifier; the W/ prefix marks a weak validator
Last-ModifiedWed, 07 Oct 2026 08:00:00 GMTa date validator, coarser than an ETag (one-second resolution)
If-None-Match"v42"request: send the body only if the ETag differs; otherwise the answer is 304 Not Modified
If-Modified-SinceWed, 07 Oct 2026 08:00:00 GMTrequest: the date-based version of the same check; ignored when If-None-Match is present
VaryAccept-Encodingthe response depends on these request headers, so caches must store one copy per value
Age120set by caches: the estimated seconds since the response was generated or revalidated at the origin

no-cache does not mean "do not cache"; no-store does. For files with a content hash in their name, Cache-Control: max-age=31536000, immutable is the usual choice. For HTML pages, no-cache together with an ETag gives fast 304 responses without serving stale pages.

Content and negotiation

HeaderExampleMeaning
Content-Typetext/html; charset=utf-8media type of the body; for text, include the charset
Content-Length3495body size in bytes
Content-Encodingbr, gzip, zstdthe body is compressed; the client decompresses it transparently
Transfer-EncodingchunkedHTTP/1.1 only: the body arrives in chunks of announced size; not used in HTTP/2 or HTTP/3
Content-Dispositionattachment; filename="report.pdf"ask the browser to download rather than display; filename*=UTF-8''r%C3%A9sum%C3%A9.pdf for non-ASCII names
Content-Languageenlanguage of the content
Acceptapplication/json, text/html;q=0.8request: media types the client prefers; q weights from 0 to 1, default 1
Accept-Encodinggzip, br, zstdrequest: compression formats the client can decode
Accept-Languageen-GB, en;q=0.9, de;q=0.5request: preferred languages

X-Content-Type-Options: nosniff stops browsers from guessing a different type than Content-Type says. Without it, a text upload served as text/plain can be treated as a script or a stylesheet in some contexts.

Ranges and resumed downloads

HeaderExampleMeaning
Accept-Rangesbytesresponse: byte ranges are supported
Rangebytes=0-499, bytes=500-, bytes=-200request: first 500 bytes, everything from byte 500, the last 200 bytes
Content-Rangebytes 0-499/8000response to a range request, sent with status 206
If-Range"v42"request: send the range only if the resource is unchanged, otherwise the whole body with 200

A range that lies entirely outside the resource gets 416 Range Not Satisfiable, with Content-Range: bytes */8000 giving the actual length.

TaskCommand
Fetch the first 100 bytescurl -s -r 0-99 -o part.bin "$URL"
Resume an interrupted downloadcurl -C - -O "$URL"

Conditional writes

HeaderExampleMeaning
If-Match"v42"perform the PUT or DELETE only if the current ETag is still "v42"; otherwise 412 Precondition Failed
If-Unmodified-SinceWed, 07 Oct 2026 08:00:00 GMTthe date-based version of If-Match
If-None-Match*on a PUT: create only if nothing exists yet

Sending the ETag you read back in If-Match turns a read-modify-write cycle into optimistic locking: if someone else saved in between, your write fails with 412 instead of silently overwriting theirs.

Authentication and redirects

HeaderExampleMeaning
AuthorizationBearer eyJhbGciOi...request: credentials; Basic, Bearer and other schemes
WWW-AuthenticateBearer realm="api", error="invalid_token"required in a 401 response: which scheme to use and why it failed
Location/reports/42the target of a 3xx redirect, or the new resource after 201 Created; may be relative
Retry-After120 or an HTTP datewith 429 or 503: when to try again
AllowGET, HEAD, OPTIONSrequired in a 405 response: the methods that do work

Cookies

AttributeEffect
Max-Age=3600lifetime in seconds; wins over Expires; without either, the cookie lasts until the browser session ends
Expires=Wed, 07 Oct 2026 08:00:00 GMTabsolute expiry
Path=/sent only for request paths under this prefix
Domain=example.comalso sent to subdomains; without Domain, only to the exact host that set it
Securesent only over https
HttpOnlynot readable from JavaScript through document.cookie
SameSite=Laxnot sent on cross-site subrequests, but sent on top-level navigations with GET; Chrome treats a cookie without SameSite as Lax
SameSite=Strictnever sent with cross-site requests, not even when following a link
SameSite=Nonesent with cross-site requests; requires Secure

A cookie whose name starts with __Host- is accepted only with Secure, Path=/ and no Domain, so it cannot be set or overwritten for the host by a subdomain. __Secure- only requires Secure.

CORS

HeaderExampleMeaning
Originhttps://example.comrequest: scheme, host and port of the page making a cross-origin request
Access-Control-Allow-Originhttps://example.com or *which origin may read the response
Access-Control-Allow-Credentialstruethe response may be read even though the request carried cookies or HTTP authentication; not allowed together with *
Access-Control-Allow-MethodsGET, POST, PUTpreflight response: methods allowed for the actual request
Access-Control-Allow-HeadersContent-Type, Authorizationpreflight response: request headers allowed
Access-Control-Expose-HeadersETag, Locationresponse headers that scripts may read beyond the default safe list
Access-Control-Max-Age600seconds a preflight result may be cached; browsers cap it (Chrome at two hours)

A preflight is an OPTIONS request the browser sends before a cross-origin request that uses a method other than GET, HEAD or POST, a custom header, or a Content-Type other than the two form encodings and text/plain. JSON requests sent with Content-Type: application/json therefore always trigger one. When the allowed origin depends on the request, the response must also carry Vary: Origin, or a cache may hand one origin's answer to another.

CORS is enforced by browsers only. It does not protect an API from scripts, curl or other non-browser clients.

Security headers

HeaderExampleEffect
Content-Security-Policydefault-src 'self'; frame-ancestors 'none'which sources scripts, styles, images, connections and frames may come from
Strict-Transport-Securitymax-age=63072000; includeSubDomainsthe browser uses only https for this host for the given time; ignored when received over plain http
X-Content-Type-Optionsnosniffno content-type guessing
X-Frame-OptionsDENYolder way to forbid framing; CSP frame-ancestors replaces it and takes precedence where supported
Referrer-Policystrict-origin-when-cross-originhow much of the current URL is sent as Referer; this value is also the default in current browsers
Permissions-Policycamera=(), geolocation=()turns off browser features for the page and its frames
Cross-Origin-Opener-Policysame-originputs the page in its own browsing context group, cut off from cross-origin popups

Looking at headers

TaskCommand
Response headers only (HEAD request)curl -sI "$URL"
Request and response headers of a GETcurl -sv -o /dev/null "$URL" 2>&1 | grep -E "^[<>]"
Send a conditional requestcurl -sI -H 'If-None-Match: "v42"' "$URL"
Check what a preflight returnscurl -si -X OPTIONS -H "Origin: https://example.com" -H "Access-Control-Request-Method: PUT" "$URL"

Sources