Skip to content
Control Plane Labs

HTTP headers cheatsheet

A practical HTTP headers cheatsheet for requests, responses, caching, cookies, CORS, security policy, and the headers worth checking during an incident.

Control Plane Labs Staff

Published August 3, 2026

The body gets most of the attention, but headers explain a large share of production web failures. A Cache-Control mistake serves stale JavaScript; a missing Vary makes one user’s representation appear for another; a misplaced Authorization header makes a request fail before the application sees it. The definitions below follow the HTTP semantics in RFC 9110 and the caching rules in RFC 9111, rather than a framework’s shortcuts.

The request headers worth checking first

These headers describe what the client wants and what it can process:

GET /api/orders HTTP/1.1
Host: api.example.test
Accept: application/json
Accept-Encoding: gzip, br
Accept-Language: en-US,en;q=0.8
Authorization: Bearer redacted-token
If-None-Match: "orders-v42"
User-Agent: example-client/1.0

When a header value needs pattern matching, test the exact JavaScript behavior with the Regex tester before putting the expression into a proxy or client.

Host identifies the authority for HTTP/1.1 requests. In HTTP/2 and HTTP/3, the :authority pseudo-header carries the equivalent value, but application code usually sees the same logical host. The HTTP/1.1 message syntax documents the wire format; do not copy HTTP/1.1 framing assumptions into an HTTP/2 debugging tool.

Accept is a preference, not a demand. A client that sends Accept: application/json is saying that JSON is acceptable; it does not make the server return JSON if that representation does not exist. The server’s choice belongs in Content-Type, and a response that varies by Accept should also send Vary: Accept.

Content-Type describes the media type of the message body. For JSON, use application/json; for form submissions, the encoding is different. A missing or incorrect content type can make a perfectly valid body look empty to a parser. Content-Length counts the message body in bytes, while Transfer-Encoding is a framing concern for HTTP/1.1. An intermediary should not blindly copy either header when it changes the body.

Accept-Encoding advertises compression formats. The response’s Content-Encoding says which one was selected, such as gzip or br. The compressed bytes are not a different media type: Content-Type still describes the uncompressed representation. A response that changes compression based on the request must include Vary: Accept-Encoding, or a shared cache can serve the wrong representation.

Authorization carries credentials and should be sent only over an encrypted connection to the intended origin. Do not put bearer tokens in URLs: URLs land in browser history, proxy logs, analytics systems, and the Referer chain. The HTTP authentication framework in RFC 9110 defines the challenge and credential model; the choice of bearer, basic, or another scheme is an application decision.

Conditional requests are the fastest way to validate cache behavior. A client can send If-None-Match with an earlier ETag; if the representation has not changed, the server returns 304 Not Modified with no response body. The HTTP status 304 reference covers the response-side rules. If-Modified-Since is a date-based fallback, but entity tags are usually more precise when a deployment can publish them.

Response headers that explain what the server did

Start with these on every response:

Header What it answers Common mistake
Content-Type What format is the body? Returning HTML with application/json
Content-Length How many body bytes are present? Reusing it after changing the body
Content-Encoding Was the body compressed? Decompressing twice in a proxy
Cache-Control May this response be stored and for how long? Using no-cache when the goal is no storage
ETag Which representation version is this? Generating a new tag on every request
Last-Modified When was the representation last changed? Treating it as a content hash
Location Where should the client go next? Emitting a relative or unsafe redirect
Vary Which request fields change the representation? Omitting it for language or encoding negotiation
Set-Cookie Which cookie should the browser store? Missing Secure, HttpOnly, or SameSite

Cache-Control: no-cache does not mean “never store this.” It means a cache must revalidate before reuse. Cache-Control: no-store is the directive for responses that must not be stored. private prevents a shared cache from storing a response intended for one user, while public permits shared caching when other directives allow it. The complete directive grammar is in RFC 9111 section 5.2.

For versioned static assets, a useful pattern is:

Cache-Control: public, max-age=31536000, immutable
ETag: "app-8e7d1"

That only works when the URL changes whenever the bytes change, such as app.8e7d1.js. For an HTML document or API response whose URL stays fixed, use a short freshness lifetime or revalidation instead. A year-long lifetime on /app.js is not a performance optimization if the browser has no way to learn that the file changed.

Age tells you how long a shared cache has held a response. Via identifies intermediaries, and X-Cache or CF-Cache-Status may expose vendor-specific diagnostics. These X- fields are not standardized HTTP semantics; treat them as clues, not portable application contracts. Compare them with the standard headers and the response status.

Set-Cookie is a response header, not a generic response metadata field. A cookie for a session should normally have Secure; HttpOnly; SameSite=Lax or SameSite=Strict, depending on the login flow. The HTTP state management specification defines the cookie grammar, while browser behavior around modern cookie attributes is summarized by MDN’s Set-Cookie reference. Never log complete Cookie or Set-Cookie values in an incident channel.

Security headers: use policy, not cargo cult

Security headers are controls with a browser-visible effect. Add only policies you can test and maintain:

Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'; script-src 'self'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Strict-Transport-Security tells a browser to replace future HTTP requests with HTTPS for the policy lifetime. It is defined by RFC 6797. Do not add includeSubDomains until every affected subdomain supports HTTPS, and test the policy before considering a preload submission.

Content-Security-Policy restricts where a document may load scripts, styles, images, connections, and other resources. Start with a report-only policy or a small, observable policy; a copied script-src 'self' can break analytics, payment widgets, or inline bootstraps. The W3C CSP specification is the normative reference, and MDN’s CSP guide is a useful browser-oriented index.

X-Content-Type-Options: nosniff stops browsers from guessing a different type for certain responses. It cannot fix a wrong Content-Type; it makes the wrong type fail more clearly. Referrer-Policy controls how much URL information travels in the Referer header when a page links elsewhere. strict-origin-when-cross-origin keeps the full path on same-origin requests but sends only the origin to another origin.

Access-Control-Allow-Origin is a response header for Cross-Origin Resource Sharing, not an authentication mechanism. If a request includes credentials, the server cannot answer with Access-Control-Allow-Origin: *; it must name an allowed origin and return Access-Control-Allow-Credentials: true. A response that varies by origin should send Vary: Origin. The Fetch standard’s CORS section defines the browser protocol.

A repeatable header-debugging command

Use a verbose request that follows redirects but keeps headers visible:

curl --head --location --max-redirs 5 \
  --connect-timeout 5 --max-time 15 \
  https://example.com/

curl --verbose --compressed \
  -H 'Accept: application/json' \
  https://api.example.com/health

--head asks for headers without downloading the body, but it can trigger a different code path on servers that do not implement HEAD correctly. Use --verbose with a normal GET when you need to compare the request and response. --location shows every redirect hop; inspect the status, Location, and security headers on each hop rather than checking only the final page.

When an asset is stale, record the URL, status, Age, Cache-Control, ETag, Last-Modified, and Vary values from both a warm and cold request. When a browser-only failure occurs, compare Origin, the Access-Control-* fields, and the preflight OPTIONS response. When a login fails, redact the token and compare Cookie, Set-Cookie, Secure, SameSite, and the request scheme. This turns “the browser says CORS” into a small, testable difference.

Frequently asked questions

What is the most important HTTP header?+
There is no single winner. Start with Content-Type, Cache-Control, Vary, and the status code; add authentication and security policy headers according to the request.
Is no-cache the same as no-store?+
No. no-cache permits storage but requires revalidation before reuse. no-store tells caches not to store the response.
Why does a CORS error mention a missing header?+
The browser blocked a cross-origin response because the server did not return a matching Access-Control-Allow-Origin value, or because the preflight response did not allow the requested method or headers.
Should I add every security header?+
No. Add policies you understand and test. A restrictive Content-Security-Policy or HSTS includeSubDomains setting can break a site when its dependencies are not ready.
How can I see headers before the body?+
Use curl --head for a HEAD request, or curl --verbose for a normal GET when you need to verify that the server handles HEAD the same way as GET.

Tags: #http, #headers, #web-development, #security, #cheatsheet