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.
Read next
- Inspect a live response with the HTTP header inspector.
- See the HTTP status code reference.
- Read How to read a TLS certificate.
Frequently asked questions
What is the most important HTTP header?+
Is no-cache the same as no-store?+
Why does a CORS error mention a missing header?+
Should I add every security header?+
How can I see headers before the body?+
Tags: #http, #headers, #web-development, #security, #cheatsheet