Common HTTP Header Fields Reference
HTTP’s header section is the protocol’s actual extension mechanism: the methods and status codes have barely changed in three decades, while nearly everything HTTP has learned to do — caching, negotiation, authentication, cross-origin sharing, transport security, proxy transparency — arrived as field definitions. This note is the reference layer for the fields that carry real weight, grouped by the job they do, each with its syntax from the defining specification and a note on when to reach for it. The core set is RFC 9110 §§8, 10, 11, 12 and 13 (June 2022, STD 97), with caching fields in RFC 9111, framing fields in RFC 9112, proxy disclosure in RFC 7239, transport security in RFC 6797, and the browser-platform fields (CORS,
nosniff, CSP) in the WHATWG Fetch Standard and CSP Level 3. The organising distinction that runs through everything below — and the one intermediaries actually enforce — is hop-by-hop versus end-to-end: whether a field is meant for the next connection or for every recipient in the chain.
This is a §7 reference leaf of the Protocol Wire Formats and Headers MOC. Its companion is HTTP Methods and the Safe Idempotent Cacheable Matrix. The conceptual siblings that teach rather than tabulate are HTTP Semantics Methods and Status Codes (negotiation, validators, ranges) and HTTP Caching (the freshness model these directives feed).
Mental Model — Fields Grouped by the Job They Do
A header block looks like an unordered pile of Name: value lines, and reading it as one is why people cannot remember which field goes where. Every field belongs to exactly one of a small number of jobs, and once you can name the job, the syntax and the placement follow.
mindmap root(("HTTP<br/>field<br/>section")) Representation metadata Content-Type Content-Length Content-Encoding Content-Language Content-Location Validators and conditionals ETag Last-Modified If-None-Match If-Modified-Since If-Match If-Unmodified-Since If-Range Caching Cache-Control Age Expires Vary Negotiation Accept Accept-Encoding Accept-Language Accept-Charset Authentication Authorization WWW-Authenticate Proxy-Authorization Proxy-Authenticate Connection control Host Connection Upgrade Transfer-Encoding TE Cross-origin Origin Access-Control-Allow-Origin Access-Control-Allow-Methods Access-Control-Expose-Headers Forwarding Forwarded X-Forwarded-For Via Security policy Strict-Transport-Security Content-Security-Policy X-Content-Type-Options
The taxonomy this note follows. What it shows: nine jobs, each owning a coherent family of fields. The insight to take: the groups are not arbitrary shelving — they predict behaviour. Everything in Connection control is hop-by-hop and gets stripped by proxies; everything in Representation metadata describes the bytes and survives to the origin; everything in Caching is read by a machine you do not operate; everything in Security policy is a directive to a browser and is ignored by everything else.
The hop-by-hop / end-to-end split
This is the one structural rule worth learning before any individual field. RFC 9110 §7.6.1 defines the Connection header field as the declarative mechanism for it: a sender lists control options for the current connection, and intermediaries MUST parse Connection before forwarding, remove any field whose name matches a listed connection-option, and then remove Connection itself. That turns “which fields are only for the next hop?” from tribal knowledge into something the message states about itself. §7.6.1 goes further and says intermediaries SHOULD also strip fields known to require removal whether or not they were listed, naming Proxy-Connection, Keep-Alive, TE, Transfer-Encoding and Upgrade.
flowchart LR C["Client"] -->|"Host: api.example.com<br/>Authorization: Bearer …<br/>Accept-Encoding: gzip<br/>Connection: upgrade, keep-alive<br/>Upgrade: websocket<br/>Transfer-Encoding: chunked"| P["Reverse proxy"] P -->|"Host: api.example.com<br/>Authorization: Bearer …<br/>Accept-Encoding: gzip<br/>Forwarded: for=…;proto=https<br/>Via: 1.1 proxy"| O["Origin server"] STRIP["STRIPPED at the hop:<br/>Connection · Upgrade<br/>Keep-Alive · Transfer-Encoding · TE<br/>RFC 9110 §7.6.1"] ADD["ADDED by the hop:<br/>Forwarded (RFC 7239)<br/>Via (RFC 9110 §7.6.3)"] P -.-> STRIP P -.-> ADD
What survives a proxy hop and what does not. What it shows: the end-to-end fields (Host, Authorization, Accept-Encoding) pass through untouched, the connection-control fields are consumed and removed, and the proxy contributes disclosure fields of its own. The insight to take: this is why Transfer-Encoding: chunked on the client side tells you nothing about the origin side, and why security decisions must never be made on a hop-by-hop field. It is also the precise seam where request smuggling lives — see the Transfer-Encoding entry below.
Field syntax fundamentals
Four grammar facts, from RFC 9110 §5, cover almost all parsing:
field-name = token, and names are compared case-insensitively. HTTP/2 and HTTP/3 go further and require lowercase on the wire (The HPACK Header Compression Format, The QPACK Field Compression Format).- List fields use the
#ruleconvention: comma-separated members with optional whitespace, and — critically — a list field may be split across multiple field lines with identical semantics.Accept: a+Accept: bisAccept: a, b. - Singleton fields (
Content-Type,Content-Length,Age,ETag) are not lists. §8.3 notes thatContent-Typeis nonetheless sometimes generated multiple times, that recipients often cope by taking the last valid member, and that this divergence in error handling is itself an interoperability and security problem. - Shared value forms:
token,quoted-string,parameters(;name=valuetails),HTTP-date(the fixed IMF-fixdate form, always GMT),delta-seconds(1*DIGIT), andweight(;q=a value from 0 to 1, §12.4.2).
Newer fields increasingly use Structured Fields, RFC 9651 — a typed grammar of Items, Lists and Dictionaries intended to make new fields easier and safer to define. RFC 9651 obsoletes RFC 8941; if you are defining a field today, this is the grammar to use.
Representation Metadata — Describing the Bytes
These describe the representation being transferred, and they are end-to-end.
| Field | Syntax (RFC 9110) | Section | When to reach for it |
|---|---|---|---|
Content-Type | media-type = type "/" subtype parameters | §8.3 | Always, when there is a body. Content-Type: text/html; charset=utf-8 |
Content-Length | 1*DIGIT — octets, decimal | §8.6 | Whenever the size is known before the header section ends |
Content-Encoding | #content-coding | §8.4 | To mark a compressed representation: gzip, br, deflate, zstd |
Content-Language | #language-tag | §8.5 | Natural-language audience of the representation |
Content-Location | absolute-URI / partial-URI | §8.7 | The specific URI of this representation — load-bearing for cacheable POST/PATCH |
Content-Type defines both the data format and how a recipient is intended to process it, applied after any Content-Encoding is decoded (§8.3). If it is absent, a recipient may assume application/octet-stream or sniff the bytes — and §8.3 warns plainly that MIME sniffing risks incorrect conclusions and consequent security exposure, including privilege escalation. That warning is the entire motivation for X-Content-Type-Options: nosniff further down.
Content-Length is a decimal count of octets, and §8.6 says a recipient MUST anticipate large numerals and guard against integer overflow or precision loss — a parser-hardening requirement, not a stylistic note. There are hard prohibitions: no Content-Length on any 1xx or 204 response, and none on a 2xx response to CONNECT. On HEAD and 304 responses it may appear, but only if it equals what a GET/200 would have carried.
Content-Encoding versus Transfer-Encoding is the distinction people conflate most. §8.4 states it exactly: the codings listed in Content-Encoding are a characteristic of the representation — the representation is the coded form, and its other metadata describes that coded form. Transfer-Encoding (RFC 9112 §6.1) is by contrast a property of the message, applied in transit, and any recipient in the chain may add or remove it. Consequence: an ETag covers the gzipped bytes when Content-Encoding: gzip is in play, but is unaffected by chunking. A representation whose media type is inherently compressed does not restate that in Content-Encoding.
Content-Location looks decorative and is not. Per RFC 9110 §9.3.3, a POST response is cacheable only when it carries explicit freshness information and a Content-Location equal to the POST target URI; RFC 5789 §2 sets the same condition for PATCH. It is also one of the URIs a cache may invalidate on an unsafe request (RFC 9111 §4.4).
Validators and Conditional Requests
A validator is an opaque token identifying a representation’s state; a conditional request carries a precondition built from one, which the server evaluates before acting (RFC 9110 §8.8, §13).
| Field | Direction | Syntax | Section |
|---|---|---|---|
ETag | response | [ "W/" ] DQUOTE *etagc DQUOTE | §8.8.3 |
Last-Modified | response | HTTP-date | §8.8.2 |
If-None-Match | request | "*" / #entity-tag | §13.1.2 |
If-Modified-Since | request | HTTP-date | §13.1.3 |
If-Match | request | "*" / #entity-tag | §13.1.1 |
If-Unmodified-Since | request | HTTP-date | §13.1.4 |
If-Range | request | entity-tag / HTTP-date | §13.1.5 |
ETag’s grammar is worth reading carefully because it is not a quoted-string: it is DQUOTE *etagc DQUOTE where etagc excludes the double quote, optionally prefixed by a case-sensitive W/ weakness indicator. §8.8.3 notes that older specifications did define it as a quoted-string, so some recipients unescape backslashes — servers ought to avoid backslashes in entity tags. Strong is the default; an origin that cannot satisfy the strong-validator characteristics of §8.8.1 MUST mark the tag weak. ETag: "" is legal.
sequenceDiagram autonumber participant C as Client / cache participant S as Origin C->>S: GET /style.css S-->>C: 200 OK<br/>ETag: "a3f9c2"<br/>Last-Modified: Tue, 21 Jul 2026 09:00:00 GMT<br/>Cache-Control: max-age=600 Note over C: 600s elapse — the stored response is now stale C->>S: GET /style.css<br/>If-None-Match: "a3f9c2"<br/>If-Modified-Since: Tue, 21 Jul 2026 09:00:00 GMT alt representation unchanged S-->>C: 304 Not Modified (no body)<br/>updated freshness fields else representation changed S-->>C: 200 OK + new body + new ETag end
Revalidation, the dominant use of validators. What it shows: the validator issued on the first response comes back as a precondition on the second, and a 304 saves the body transfer while refreshing the freshness metadata. The insight to take: send both validators when you have both. §13.2.2 fixes an evaluation order in which If-None-Match takes precedence over If-Modified-Since, so the ETag wins where they disagree — the timestamp is a fallback for caches or servers that lost the tag, not a competing signal.
The write-side use is the more interesting one. If-Match with a strong ETag turns an unconditional write into a compare-and-swap: the server applies the PUT or PATCH only if the resource is still in the state the client last saw, and otherwise answers 412 Precondition Failed. This is the standard cure for lost updates, and RFC 5789 §2 explicitly names it as the way to make a non-idempotent PATCH safe to retry — the mechanism catalogued in HTTP Methods and the Safe Idempotent Cacheable Matrix. If-Range is the resumption guard: paired with Range, it means “send the missing bytes if unchanged, otherwise send the whole new thing”, which is what stops a resumed download from stitching together two different files.
Caching Fields
Cache-Control (RFC 9111 §5.2) is a list of directives — token [ "=" ( token / quoted-string ) ] — compared case-insensitively. §5.2 states two structural rules that catch people out: directives are unidirectional (a directive in the request does not imply the same directive in the response), and a proxy MUST pass cache directives through in forwarded messages whether or not they mean anything to it, because it is not possible to target a directive at one specific cache.
Response directives (§5.2.2) — a cache MUST obey these:
| Directive | Argument | Effect |
|---|---|---|
max-age=N | delta-seconds | Response is stale once its age exceeds N |
s-maxage=N | delta-seconds | Overrides max-age and Expires for shared caches; also implies proxy-revalidate and permits reuse of an Authorization-bearing response |
no-cache | optional #field-name | Must not be reused without successful revalidation. Qualified form limits only the named fields |
no-store | — | Must not be stored at all, in private or shared caches; best-effort removal from volatile storage |
private | optional #field-name | Shared caches must not store it; private caches may |
public | — | Explicitly cacheable even where it otherwise would not be — e.g. behind Authorization |
must-revalidate | — | Once stale, must not be reused without successful origin validation, even when disconnected |
proxy-revalidate | — | As above, but shared caches only |
no-transform | — | Intermediaries must not transform the content |
must-understand | — | Cache only if it implements that status code’s caching rules |
Two of these hide sharp edges. no-cache does not mean “do not cache” — it means store freely but always revalidate before reuse; no-store is the directive that means what people think no-cache means. And must-revalidate is a correctness weapon, not a tuning knob: §5.2.2.2 says a cache must never ignore it, must generate an error rather than serve stale when disconnected, and that servers ought to use it if and only if a validation failure could cause incorrect operation — naming a silently unexecuted financial transaction as the example. The recommended error is 504 Gateway Timeout.
The request directives (§5.2.1) are advisory — caches MAY implement them: max-age, max-stale, min-fresh, no-cache, no-store, no-transform, and only-if-cached (answer from cache or return 504).
Three widely deployed extensions live outside RFC 9111. RFC 5861 defines stale-while-revalidate=N (serve stale for up to N seconds after expiry while revalidating asynchronously, without blocking) and stale-if-error=N (serve stale when the origin errors). RFC 8246 defines immutable, which asserts the origin will not change the representation during its freshness lifetime, so clients should skip revalidating even on reload — the canonical pairing being max-age=31536000, immutable for content-hashed assets.
Age (§5.1) is the sender’s estimate, in seconds, of the time since the response was generated or validated at the origin. Its presence implies the response was not generated by the origin for this request — but §5.1 is careful that its absence does not imply the origin was contacted. It is the field to read first when debugging a CDN.
Expires (§5.3) is an HTTP-date and is the legacy path: if max-age is present a recipient MUST ignore Expires, and a shared cache MUST ignore it when s-maxage is present. An invalid date — famously the literal 0 — MUST be read as already expired.
Vary (RFC 9110 §12.5.5) is the field that makes shared caching survive content negotiation. It lists the request field names that influenced representation selection, and it does two jobs: it expands the cache key so a stored response is only reused for requests with matching values for the listed fields, and it tells a user agent that negotiation happened. Vary: * means selection depended on something outside the message syntax — and a proxy MUST NOT generate *. Omitting Vary: Accept-Encoding on a negotiated response is the classic mechanism by which a CDN serves gzipped bytes to a client that never asked for them.
Content Negotiation — the Accept Family
Proactive negotiation: the client states preferences, the server picks (RFC 9110 §12).
| Field | Syntax | Section |
|---|---|---|
Accept | #( media-range [ weight ] ), media-range = "*/*" / type "/*" / type "/" subtype + parameters | §12.5.1 |
Accept-Encoding | #( codings [ weight ] ), codings = content-coding / "identity" / "*" | §12.5.3 |
Accept-Language | #( language-range [ weight ] ) | §12.5.4 |
Accept-Charset | #( ( token / "*" ) [ weight ] ) — deprecated by §12.5.2 | §12.5.2 |
The quality value (§12.4.2) is the shared knob: ;q= a number normalised to the range 0 through 1 where 0.001 is least preferred, 1 is most preferred, q=0 means not acceptable, and an absent q defaults to 1. At most three digits after the decimal point may be generated. RFC 9110 removed the old accept-ext extension grammar and advises senders to put q last, after all media-range parameters, while recipients should treat any parameter named q as the weight regardless of position.
Accept-Encoding’s three-rule acceptability test (§12.5.3) is worth having straight: no Accept-Encoding at all means any coding is acceptable; an unencoded representation is acceptable by default unless specifically excluded by identity;q=0 or a *;q=0 with no more specific identity entry; and a listed coding is acceptable unless it carries q=0. Note that these are request fields that RFC 9110 also permits in responses, where they advertise what the server would prefer to receive next time.
Authentication
| Field | Direction | Syntax | Section |
|---|---|---|---|
WWW-Authenticate | response | #challenge | §11.6.1 |
Authorization | request | credentials | §11.6.2 |
Proxy-Authenticate | response (407) | #challenge | §11.7.1 |
Proxy-Authorization | request | credentials | §11.7.2 |
Authentication-Info | response | #auth-param | §11.6.3 |
Both challenge and credentials share the shape auth-scheme [ 1*SP ( token68 / #auth-param ) ] — a scheme name followed by either a token68 blob (the Base64-ish form used by Basic and Bearer) or comma-separated name=value parameters (used by Digest). §11.6.1 makes WWW-Authenticate mandatory on a 401 response, with at least one challenge, and it may also appear on other responses to hint that credentials would change the answer. Parsing it is genuinely hard: the field may occur multiple times, may carry several challenges on one line, and each challenge may carry a comma-separated parameter list — §11.6.1 warns that some user agents do not handle multiple challenges on one field line, so sending them that way may not interoperate.
Authorization is end-to-end and must not be modified by a forwarding proxy (§11.6.2). Its interaction with caching is the trap: RFC 9111 §3.5 stops a shared cache from storing a response to a request bearing Authorization unless public, must-revalidate, or s-maxage explicitly permits it. Conversely, RFC 9110 §12.5.5 notes there is no need to list Authorization in Vary, because the caching rules already handle it.
Connection Control
| Field | Scope | Syntax | Section |
|---|---|---|---|
Host | end-to-end (routing) | uri-host [ ":" port ] | RFC 9110 §7.2 |
Connection | hop-by-hop | #connection-option (token) | §7.6.1 |
Upgrade | hop-by-hop | #protocol, protocol = name ["/" version] | §7.8 |
Transfer-Encoding | hop-by-hop | #transfer-coding | RFC 9112 §6.1 |
TE | hop-by-hop | #t-codings | RFC 9110 §10.1.4 |
Via | end-to-end (accumulating) | #( received-protocol RWS received-by [ RWS comment ] ) | §7.6.3 |
Host carries the target URI’s authority so one server can distinguish resources across many host names. A user agent MUST send it unless it is sending the information as the HTTP/2 or HTTP/3 :authority pseudo-header, and SHOULD send it first in the header section. §7.2 flags it as a frequent malware target for poisoning a shared cache or redirecting a request, singling out interception proxies that use it for routing or as a cache key without first checking that the intercepted connection really targets a valid address for that host.
Upgrade invites a switch to another protocol on the same connection, in descending preference order. A server that answers 101 Switching Protocols MUST echo Upgrade naming the new protocol, MUST NOT switch to one the client did not offer, and — the rule that ties the group together — a sender of Upgrade MUST also list Upgrade as a Connection option so intermediaries do not forward it. §7.8 also fixes the interaction with Expect: 100-continue: the 100 Continue goes first, then the 101. This is the mechanism behind the WebSocket handshake (WebSocket Protocol, The WebSocket Frame Format).
Transfer-Encoding is where framing becomes a security problem. RFC 9112 §6.1 requires every recipient to be able to parse chunked, requires chunked to be the final coding, and defines Transfer-Encoding as overriding Content-Length — a decision §6.1 explains historically, since early implementations sent both (chunked framing plus an estimated length for progress bars). §6.3’s precedence list then says a message carrying both might be a smuggling or response-splitting attempt and ought to be treated as an error; an intermediary that forwards it MUST first remove Content-Length. A server MAY reject such a request outright but MUST close the connection after responding either way. Full treatment: Chunked Transfer Encoding Format.
Cross-Origin Resource Sharing
CORS is defined by the WHATWG Fetch Standard, a Living Standard (last updated 2 July 2026 as consulted). It is browser-enforced: these fields mean nothing to curl.
| Field | Direction | Meaning |
|---|---|---|
Origin | request | The requesting context’s serialized origin (may be null) |
Access-Control-Request-Method | preflight request | The method a subsequent CORS request might use |
Access-Control-Request-Headers | preflight request | The headers a subsequent CORS request might use |
Access-Control-Allow-Origin | response | Whether the response may be shared — the literal Origin value, null, or * |
Access-Control-Allow-Credentials | response | Whether it may be shared when credentials mode is include |
Access-Control-Allow-Methods | preflight response | Methods supported for CORS purposes |
Access-Control-Allow-Headers | preflight response | Headers supported for CORS purposes |
Access-Control-Max-Age | preflight response | Seconds the allow-methods/allow-headers answer may be cached — default 5 |
Access-Control-Expose-Headers | response | Which response headers script may read |
sequenceDiagram autonumber participant JS as Page at https://app.example participant B as Browser participant S as https://api.example Note over JS,S: fetch("https://api.example/orders", {method:"PATCH",<br/>headers:{"Content-Type":"application/json"}}) JS->>B: request is not CORS-safelisted<br/>(PATCH; JSON Content-Type) B->>S: OPTIONS /orders (CORS-preflight)<br/>Origin: https://app.example<br/>Access-Control-Request-Method: PATCH<br/>Access-Control-Request-Headers: content-type S-->>B: 204 No Content<br/>Access-Control-Allow-Origin: https://app.example<br/>Access-Control-Allow-Methods: PATCH<br/>Access-Control-Allow-Headers: content-type<br/>Access-Control-Max-Age: 600 B->>S: PATCH /orders (the real request)<br/>Origin: https://app.example S-->>B: 200 OK<br/>Access-Control-Allow-Origin: https://app.example<br/>Access-Control-Expose-Headers: etag B-->>JS: response, with ETag readable
The preflight round trip and what triggers it. What it shows: a preflight is an extra OPTIONS request that must succeed before the real one is sent, and its answer is cacheable for Access-Control-Max-Age seconds. The insight to take: the default max-age is 5 seconds, which is effectively no caching — leaving it unset means paying a full extra round trip on nearly every cross-origin call. And the preflight was triggered by two choices that look innocuous: the method and the JSON content type.
Three lists decide whether you pay for a preflight. A CORS-safelisted request-header (values under 128 bytes) is Accept, Accept-Language, Content-Language, Range, or Content-Type — but Content-Type only when its essence is application/x-www-form-urlencoded, multipart/form-data or text/plain. That is why application/json forces a preflight. The CORS-safelisted response-header names that script may read without help are Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified and Pragma; anything else — including ETag — must be named in Access-Control-Expose-Headers. And a long list of forbidden request-headers cannot be set from script at all, including Host, Origin, Connection, Cookie, Referer, Content-Length, Transfer-Encoding and Upgrade.
The rule everyone eventually hits: * is not allowed when credentials are involved. A response shared with credentials must echo a concrete origin and also carry Access-Control-Allow-Credentials.
Forwarding — Disclosing the Proxy Chain
Forwarded (RFC 7239, Petersson & Nilsson, June 2014) standardises what the X-Forwarded-* family did informally. Syntax: Forwarded = 1#forwarded-element, each element a semicolon-separated list of token "=" ( token / quoted-string ) pairs, with four defined parameters — by (the proxy’s inbound interface), for (the node that made the request to the proxy), host (the original Host), and proto (http or https). Because : and [ ] are not valid in a token, IPv6 addresses must be quoted: for="[2001:db8:cafe::17]:4711". Each parameter may appear at most once per element, and names are case-insensitive.
RFC 7239 §7.4 gives the exact translation from the legacy field — X-Forwarded-For: 192.0.2.43, 2001:db8:cafe::17 becomes Forwarded: for=192.0.2.43, for="[2001:db8:cafe::17]" — and warns that a lossless conversion may be impossible when several X-Forwarded-* fields coexist, because their relative insertion order is unrecoverable.
Three cautions the RFC states outright. It defaults to off: §4 says that because of the data’s sensitivity the field should be disabled by default and each parameter configured individually, and that it is for requests only, never responses. It cannot be trusted: §8.1 says every node on the path — including the client — may modify it, so the usual whitelist-the-proxies approach still leaves the pre-proxy chain untrustworthy and, without secured proxy-to-endpoint links, tamperable. It leaks: §8.2 says the field can reveal internal network structure, prescribes obfuscated identifiers or egress-side stripping, and adds a neat cross-cutting warning — do not allow TRACE where Forwarded is in use, since the echo would place the whole proxy chain in the response body. Via (RFC 9110 §7.6.3) is the neighbouring field that discloses only the intermediaries themselves; §7.3 of RFC 7239 notes the two cannot be reliably correlated, since different proxies update different subsets.
Uncertain
Verify: the precise, currently deployed semantics of the non-standard
X-Forwarded-For,X-Forwarded-ProtoandX-Forwarded-Hostfields — element ordering, handling of multiple field lines, and which element a given reverse proxy treats as the client. Reason: these fields have no defining specification; RFC 7239 referencesX-Forwarded-Foronly to describe migration away from it, and behaviour differs between nginx, HAProxy, Envoy, and the major cloud load balancers. Trusting the wrong element is a live source of IP-spoofing and rate-limit-bypass bugs. To resolve: read the specific proxy’s documentation for its trusted-hop configuration rather than assuming a common convention.#uncertain
Security Policy Headers
These are instructions to a browser. They are response-only, and non-browser clients ignore them.
Strict-Transport-Security (RFC 6797, HTTP Strict Transport Security, November 2012). Syntax: [ directive ] *( ";" [ directive ] ) with max-age REQUIRED and includeSubDomains optional and valueless. Directive order is insignificant, each may appear at most once, names are case-insensitive, and a user agent MUST ignore the entire field if any part fails to parse — an unusually strict all-or-nothing rule that makes a stray directive silently disable your policy. max-age=0 cancels the policy for that host, and §6.2 notes it does so including any asserted includeSubDomains, which the RFC shows is ignored when max-age is zero.
Strict-Transport-Security: max-age=31536000; includeSubDomains
Uncertain
Verify: the status of the widely used
preloaddirective. It does not appear anywhere in RFC 6797 — a full-text search of the RFC returns zero occurrences. It is a browser-vendor convention gating inclusion in the HSTS preload list shipped inside browsers, not an IETF-registered directive, and its exact requirements (minimummax-age, mandatoryincludeSubDomains, apex-domain rules) are set by that list’s operators rather than by a standard. To resolve: consult the preload list submission requirements directly before relying on any specific threshold.#uncertain
Content-Security-Policy and Content-Security-Policy-Report-Only (CSP Level 3, a W3C Working Draft as consulted — recommendation-track but not yet a Recommendation, so treat details as movable). The value is a series of semicolon-delimited directives, each a name plus a space-separated value list. The families are fetch directives (default-src, script-src, style-src, img-src, connect-src, font-src, frame-src, media-src, object-src, manifest-src, worker-src, child-src, plus the -elem/-attr refinements), document directives (base-uri, sandbox), navigation directives (form-action, frame-ancestors), and reporting directives (report-uri, now deprecated in favour of report-to, which routes through the Reporting API). default-src is the fallback for the fetch directives. Source expressions include 'self', 'none', 'unsafe-inline', 'unsafe-eval', 'strict-dynamic', 'unsafe-hashes', 'report-sample', 'wasm-unsafe-eval', plus nonce-… and sha256-/sha384-/sha512- hashes. The modern nonce-based pattern:
Content-Security-Policy: default-src 'self'; script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'; report-to csp-endpoint
Deploy with Content-Security-Policy-Report-Only first — it reports violations without blocking, which is the only sane way to discover what a real policy would break.
X-Content-Type-Options: nosniff is defined in the Fetch Standard, and its ABNF is exactly the single case-insensitive token nosniff. The algorithm is narrower than its reputation suggests. Fetch’s “should response to request be blocked due to nosniff?” runs: if nosniff is absent, allow; extract the MIME type; then block only if the request destination is script-like and the type is not a JavaScript MIME type, or the destination is style and the type’s essence is not text/css. Everything else is allowed. The spec explains why: only script-like and style destinations carry the relevant exploits, and applying it to image proved incompatible with deployed content. So nosniff stops a user-uploaded file being executed as script — it does not universally enforce your Content-Type. The adjacent field in the same area is Cross-Origin-Resource-Policy, whose value is one of the case-sensitive tokens same-origin, same-site, or cross-origin.
Failure Modes and Common Misunderstandings
no-cache misread as “do not cache.” It permits storage and requires revalidation. no-store is the one that forbids storage — and RFC 9111 §5.2.2.5 is candid that even it is not a reliable privacy mechanism, since a malicious or compromised cache may ignore it.
Missing Vary behind a shared cache. The negotiated response gets served to clients whose Accept-Encoding or Accept-Language never matched. The symptom is the classic “works for me, garbled for them”, and the fix is one field.
Treating X-Forwarded-For as authoritative. It is client-supplied text with no defining specification. Every rate limiter and geo-block built on the leftmost element without a trusted-hop configuration is bypassable by sending the field yourself.
Assuming ETag is readable from browser JavaScript. It is not a CORS-safelisted response-header, so a cross-origin fetch sees nothing unless the server sends Access-Control-Expose-Headers: ETag. This is the single most common “my conditional requests don’t work in the browser” cause.
Content-Encoding versus Transfer-Encoding confusion. Applying gzip via Transfer-Encoding when you meant Content-Encoding produces a representation whose validators and length no longer describe what the client stores. §8.4’s framing settles it: content coding is part of the representation; transfer coding is part of the message.
A malformed HSTS directive disabling the whole policy. RFC 6797 §6.1’s rule 4 makes the field all-or-nothing on syntax errors. A typo does not degrade the policy — it removes it.
Sending Connection: keep-alive in HTTP/2 or HTTP/3. Connection-specific fields are prohibited in those versions; see The HTTP 2 Frame Format and HTTP 2 Multiplexing and HPACK. Copying an HTTP/1.1 header set forward is a protocol error, not a harmless redundancy.
See Also
- HTTP Methods and the Safe Idempotent Cacheable Matrix — the companion §7 reference leaf: method properties, retry safety, and the
Idempotency-Keyconvention. - HTTP Semantics Methods and Status Codes — the conceptual sibling: what representations, negotiation, validators and ranges mean, with the precondition evaluation order.
- HTTP Caching — RFC 9111 in depth: the write gate, the freshness and age arithmetic, revalidation, and shared-versus-private caches.
- The HTTP Status Code Taxonomy and Reference · Choosing the Right HTTP Status Code — the codes these fields accompany.
- HTTP 1.1 Message Anatomy on the Wire · Chunked Transfer Encoding Format — how a field section is framed on the wire, and the smuggling attacks at the
Transfer-Encoding/Content-Lengthseam. - The HPACK Header Compression Format · The QPACK Field Compression Format — how these same fields are encoded in HTTP/2 and HTTP/3, including the lowercase-name requirement and the static tables.
- HTTP Cookies and State Management — RFC 6265’s
Cookie/Set-Cookie, deliberately excluded here as its own topic. - Content Delivery Networks and Edge Caching — where
Cache-Control,AgeandVaryare read by machines you do not operate. - MOC: Protocol Wire Formats and Headers MOC §7 — HTTP Status Codes and Methods Reference.