HTTP headers carry metadata and control information about a request, a response, or its content. They tell a server which host the client wants, describe a JSON payload, carry credentials, control caching, negotiate compression, set cookies, and propagate tracing context.
Consider a simple API request:
The request target identifies an order, but the fields around it answer several other questions:
Headers are therefore not decorative key-value pairs. They participate in routing, message framing, security, browser behavior, and the meaning of the exchange. A backend that mishandles them can return the wrong representation, expose private data through a shared cache, trust a spoofed client address, or create an ambiguity that different intermediaries interpret differently.
This chapter develops a practical model of HTTP fields and then examines the fields engineers most often read, emit, and debug.
In an HTTP/1.1 message, each header field appears on a line:
For example:
The field name is case-insensitive. These names identify the same field:
Software should therefore perform case-insensitive field-name lookup. HTTP/2 and HTTP/3 require field names to be encoded in lowercase, so an application must never depend on the capitalization it received.
Field values do not share one universal syntax. Each field defines its own grammar. A media type, a date, an entity tag, and a list of encodings all have different rules:
Do not casually lowercase, split, or comma-join every value. Whether capitalization matters, whether whitespace is allowed, and whether commas separate items all depend on the specific field.
There is no whitespace between a field name and its colon:
Spaces after the colon are conventional and are removed when a recipient parses the value. Historical HTTP allowed a field value to continue on a folded line, but that syntax is obsolete. Modern senders should place a field line on one line, and recipients should reject or carefully normalize obsolete folding rather than passing it through ambiguously.
A field value must not contain raw carriage-return or line-feed characters. Accepting those characters from application input can turn one intended field into several:
This is called header injection or response splitting. Mature HTTP libraries normally prevent it, but applications should still validate untrusted values and use structured library APIs instead of building raw field lines through string concatenation.
Some fields allow a comma-separated list:
A sender might transmit the same logical list as repeated field lines:
Combining repeated lines is safe only when that field's definition permits list syntax. Set-Cookie is the important exception: each cookie must remain a separate field line because an Expires attribute can itself contain a comma.
Do not place these two values on one comma-separated Set-Cookie line.
The order of fields with different names is generally insignificant. When a list is split across repeated lines, however, a proxy must preserve the order of its values while combining or forwarding them.
Headers appear before content:
The empty line ends the header section. In HTTP/1.1, it is essential because the following bytes may be content. HTTP/2 and HTTP/3 encode fields in binary header blocks rather than text lines, but applications see essentially the same field names and values.
HTTP can also carry a trailer section after the content. Trailers are useful for metadata that cannot be computed until content has been produced, such as an integrity value. They cannot safely control routing, framing, authentication, or any other decision that must happen before the content is processed.
HTTP does not define one universal maximum header size. Servers, proxies, gateways, and frameworks impose their own limits on:
A request that works when sent directly to an application can therefore fail when it passes through a proxy with a smaller limit. Oversized cookies and large authorization tokens are frequent causes. A server can reject an excessive request field section with 431 Request Header Fields Too Large.
Limits should be finite. Unbounded header parsing consumes memory before normal application handling begins.
Different fields are consumed by different participants:
The application is not always the first or last component to inspect a field. A reverse proxy can select a backend using the requested authority, a cache can choose a stored response using Vary, and a browser can block frontend code from reading a response based on cross-origin policy.
That is why a field should be understood as part of the HTTP exchange, not merely as an application parameter.
Loading simulation...
Several request fields identify the intended host or provide context about the request's path through intermediaries.
:authorityOne IP address often serves many domains. An HTTP/1.1 client supplies the target host and optional port in Host:
A server can use that value to select a virtual host. Host is required in HTTP/1.1 requests, and an invalid or missing value makes the request invalid.
HTTP/2 and HTTP/3 ordinarily carry the same authority information in the :authority pseudo-header:
Pseudo-headers are protocol control data rather than ordinary extensible fields. They must appear before regular fields and cannot be invented by applications.
The requested authority is untrusted input. An application should compare it against an allowlist of hosts it serves. It should not blindly use Host to construct password-reset links, redirects, or security-sensitive absolute URLs. Otherwise, a forged value can cause host-header injection.
X-Forwarded-*When a reverse proxy connects to an application, the application's immediate network peer is the proxy—not the original user. A proxy can describe the earlier request using the standardized Forwarded field:
Common deployments also use de facto fields:
These values are meaningful only inside a defined trust boundary. A public client can send its own X-Forwarded-For header. If the application trusts the leftmost value without knowing which proxy sanitized or appended it, an attacker can spoof an IP address used for logs, rate limits, or access control.
A robust deployment has a clear policy:
Forwarding fields can also expose internal addresses or hostnames, so proxies should avoid adding information that downstream recipients do not need.
Via records protocol intermediaries that forwarded a message:
It helps identify forwarding loops and protocol transitions. A proxy is responsible for adding its own entry; a client-supplied Via value is not proof that the listed systems actually handled the request.
User-Agent identifies the client software:
It is useful for diagnostics and compatibility work, but it is easy to omit or forge. It is not an authentication mechanism.
Referer—with the historical misspelling in its standardized name—can identify the page from which a request originated:
Browsers can shorten or omit it according to privacy policy. Do not assume it is always present, and do not place secrets in URLs that might be copied into it.
Origin identifies a web origin, normally a scheme, host, and port without a path:
Browsers use it for cross-origin decisions and send it in situations where Referer might reveal too much path information. It can be absent, and in privacy-sensitive or opaque contexts its value can be null. A server must validate it according to the operation being protected rather than treating presence alone as proof of trust.
Representation fields explain what the content is and how it has been encoded.
Content-Type identifies the media type of request or response content:
Textual types can include a character encoding:
The field describes the representation associated with that message. If a Content-Encoding is present, the recipient removes that coding before interpreting the result according to Content-Type. A server should reject a request whose declared media type it does not support rather than guessing from the body. Likewise, a response should send the correct type so that clients do not have to infer how to interpret the bytes.
Common examples include:
The boundary parameter in a multipart type is part of the media type. Manually setting multipart/form-data while letting a library generate a different boundary produces an unreadable body; normally the library should generate both.
Content-Length gives the decimal number of bytes in the message content:
It counts bytes, not Unicode characters. After a content coding such as gzip is applied, it describes the encoded bytes sent on the wire.
This distinction matters for non-ASCII text:
Applications should let the HTTP library compute the value after serialization and encoding. A wrong length can truncate one message, consume bytes from the next message, or leave a receiver waiting for bytes that will never arrive.
Conflicting Content-Length values, or ambiguous combinations of Content-Length and Transfer-Encoding, are security-sensitive. Different proxy layers can disagree about where one request ends and another begins, enabling request-smuggling attacks. Gateways should reject ambiguous framing instead of choosing whichever interpretation is convenient.
Content-Encoding lists transformations applied to the representation:
The recipient removes the content coding before interpreting the media type. For example:
When multiple codings are present, they are listed in the order they were applied and decoded in reverse order.
Content-Encoding is different from Transfer-Encoding. Content coding is a property of the representation, such as gzip compression. Transfer coding is a message-framing mechanism used between HTTP/1.1 peers.
Content-Disposition can suggest how content should be presented:
For a download, attachment asks a user agent to offer saving rather than displaying inline. The filename is a suggestion, not a trusted filesystem path. Clients must remove path separators and other dangerous characters before using it locally.
A client can describe the representations it prefers, and the server can select an available one.
Accept lists media types the client can process:
Wildcards express broader compatibility:
A quality value, or q value, ranges from 0 to 1. Higher values are preferred, and q=0 means “not acceptable.” In this example, JSON is preferred, plain text is acceptable with lower priority, and any other type is a last resort.
If no available representation is acceptable, the server can respond with 406 Not Acceptable. Many APIs instead support one documented response type and expect clients to request it explicitly.
Accept-Encoding advertises the content codings the client can decode:
A server that chooses gzip reports the choice in the response:
Compression should be selected according to both client support and server policy. Already compressed formats such as JPEG or ZIP often gain little from another compression pass, while very small bodies can cost more CPU than the saved bytes justify.
Accept-Language expresses language preferences:
It is a preference, not a reliable declaration of a person's identity or permanent language choice. Applications commonly let an explicit user profile setting override it.
When the server selects a language-specific representation, it can describe the intended audience with Content-Language:
This field does not have to list every language that happens to appear in the content. It describes the natural language of the representation's intended audience.
When a response changes according to a request field, Vary tells caches which fields participated in selection:
A cache must keep gzip and uncompressed variants separate if the response varies on Accept-Encoding. Omitting the field can cause a cache to serve a compressed representation to a client that did not advertise support, or serve the wrong language.
Vary does not perform negotiation; it describes the dimensions the server already used. Adding many high-cardinality fields can greatly reduce cache reuse. Vary: * indicates that other aspects of the request might have influenced selection and effectively prevents reuse through normal cache matching.
HTTP has a challenge-and-response framework for authentication.
A client supplies credentials using Authorization:
The first token names the authentication scheme. The remainder follows that scheme's syntax. Other examples include Basic and Digest, though an API should use the scheme required by its security design rather than inventing a custom parsing convention inside Authorization.
Credentials are sensitive. Applications and infrastructure should redact this field from logs, traces, error reports, analytics, and debug pages. A bearer token grants access to whoever possesses it, so transport it only over HTTPS.
When credentials are missing or invalid, a 401 Unauthorized response includes one or more challenges:
The challenge tells the client which scheme it can use and can include scheme-specific parameters. It is not a place to reveal why a particular account or secret failed.
Proxy-Authorization and Proxy-Authenticate form a similar exchange with a forward proxy. They authenticate the client to that proxy, not to the origin server. An intermediary must not accidentally forward proxy credentials to the origin.
Cookies use two related fields. A server creates or updates cookies with Set-Cookie in a response:
For a later matching request, the user agent sends stored name-value pairs in Cookie:
The response attributes are not repeated in the request. The browser uses them to decide whether and when the cookie is eligible to be sent.
Important attributes include:
Domain controls which hosts can receive the cookie. Omitting it creates a host-only cookie, which is usually the safer default.Path limits which request paths match. It is a delivery rule, not a security boundary.Secure restricts transmission to secure connections.HttpOnly prevents browser scripts from reading the cookie through normal cookie APIs. It does not stop the browser from sending the cookie with requests.SameSite controls some cross-site sending. Strict is most restrictive, Lax permits common top-level navigation cases, and None requests cross-site use. Modern browsers require Secure with SameSite=None.Max-Age gives a lifetime in seconds. Expires gives an absolute expiry date. Without either, the cookie is normally treated as a session cookie.Cookie scope must be deliberately narrow. A broad domain, path, or lifetime sends the credential in more situations and increases exposure. Cookie values should also stay small because browsers attach them to every matching request, often making request headers much larger than the response content.
HttpOnly, Secure, and SameSite are valuable defenses, but none makes an unsafe application design secure by itself. In particular, a server must still apply appropriate authorization and cross-site request protections.
Never comma-combine multiple Set-Cookie fields:
This exception is especially important in proxy and framework code that generically combines repeated fields.
Validators let a client ask the server to perform an operation only if a representation is, or is not, in a known state.
An entity tag is an opaque validator selected by the server:
The quoted value has no standardized internal structure. A client should return it exactly rather than parse a version number from it.
A strong tag states that two representations are byte-for-byte equivalent for the relevant comparison. A weak tag begins with W/:
Weak tags are useful when two representations are semantically equivalent even if their bytes differ, but they are not suitable for operations that require exact representation identity.
A client with a stored response can revalidate it:
If the selected representation still matches, the server can return 304 Not Modified without sending the content. If it does not match, the server returns the current representation and its new ETag.
For a state-changing method, If-None-Match: * can mean “perform this only if a current representation does not exist.” That is useful for create-if-absent behavior.
If-Match protects a write from overwriting a change the client has not seen:
The server performs the update only if the current representation strongly matches the supplied tag. If another writer already produced version 8, the precondition fails and the server returns 412 Precondition Failed.
Without the precondition, Client A could silently overwrite Client B's update.
Last-Modified gives the server's modification time:
A client can return it in If-Modified-Since for retrieval or If-Unmodified-Since for a write. Dates have limited precision and clocks can be imperfect, so an entity tag is generally a better validator when the application can generate one reliably.
When both an applicable entity-tag condition and a date condition are present, entity tags take precedence according to HTTP's precondition rules. Applications should use their framework's conditional-request support rather than creating an ad hoc evaluation order.
Cache-Control carries directives for caches and recipients:
The most commonly encountered response directives are:
max-age=60 allows reuse while the response is fresh for 60 seconds.s-maxage=300 overrides freshness for shared caches, such as a CDN, without changing a private browser cache's max-age.private allows storage in a private cache but prohibits storage by shared caches.public explicitly permits a shared cache to store a response when it might otherwise be ineligible.no-cache allows storage but requires successful validation before reuse.no-store asks caches not to store the request or response.immutable indicates that the representation will not change while fresh, so a client need not revalidate it merely because the user refreshed.The names no-cache and no-store are easy to confuse. no-cache does not mean “do not store”; it means “do not reuse without validation.” Use no-store when storage itself is inappropriate.
A request can also carry Cache-Control, for example:
This asks caches to validate a stored response before using it for this request. It does not force every intermediary to delete its cache.
Expires expresses freshness using an absolute date:
Modern responses generally prefer Cache-Control: max-age, which is not vulnerable to disagreement about the stored response's absolute expiry time. A relevant Cache-Control directive overrides Expires.
Age is added or updated by a cache:
It estimates how many seconds have elapsed since the response was generated or successfully validated at the origin. It is not the time the cache spent transmitting the response.
Caching personalized responses requires particular care. A response containing user-specific data should not become reusable across users merely because it has a freshness lifetime. private, no-store, appropriate cache keys, and application-specific policy must match the data's sensitivity.
Several compact fields tell a client what to do next or describe a partial response.
Location identifies a URI associated with the response:
For a creation response, it identifies the primary created resource. For a redirect, it identifies the next URI. The value can be relative, in which case the client resolves it against the request URI.
An application must validate externally supplied redirect targets. Reflecting an arbitrary URL into Location creates an open redirect that attackers can use to make a trusted domain send users to a malicious site.
Allow lists methods supported by a resource:
It is required in a 405 Method Not Allowed response and can also appear in an OPTIONS response. It describes protocol capabilities, not the current user's permissions; authorization policy can still reject an otherwise supported method.
Retry-After tells a client when another attempt might be appropriate. It can contain a delay in seconds:
or an HTTP date:
It commonly accompanies 429 Too Many Requests or 503 Service Unavailable. The field is guidance, not a command to retry. A client should still apply an overall deadline, retry limit, and idempotency rules, and should add jitter when many clients might retry together.
A client can request part of a representation:
A successful single-range response describes the returned portion:
Accept-Ranges: bytes advertises support for byte ranges. Content-Range identifies the selected interval and complete length. A server that cannot satisfy the requested range uses a 416 Range Not Satisfiable response with an appropriate Content-Range.
If-Range lets a client ask for a range only while its validator still matches. If the representation changed, the server sends the complete current representation instead of splicing bytes from different versions.
Most HTTP fields describe the request, response, or representation from end to end. A smaller group controls only the connection between two adjacent participants.
A proxy must consume connection-specific fields for one hop and must not blindly forward them to the next hop.
In HTTP/1.1, Connection can name fields that apply only to the current connection:
The recipient consumes or removes both Connection and every field it names before forwarding the message. Connection: close indicates that the sender will close the connection after the current response.
This removal rule is security-critical. Forwarding a nominated field can cause the next recipient to interpret per-hop control data as an end-to-end instruction.
HTTP/1.1 can use chunked transfer coding when the sender does not know the final content length before streaming:
Each chunk begins with its byte count in hexadecimal. A zero-length chunk ends the content and can be followed by trailers.
Transfer-Encoding describes framing between peers; it is not an alternative spelling for compression. A proxy can decode chunked framing and choose different framing on the next connection while preserving the same representation.
An HTTP/1.1 message must not use both Transfer-Encoding and Content-Length as competing framing instructions. A recipient that receives such ambiguity should treat it as an error, and a proxy must not forward it unchanged.
A sender can announce fields it intends to place in a trailer:
An HTTP/1.1 receiving peer can indicate that it accepts trailers. Because TE applies only to that connection, it also names the field in Connection:
Trailer support is not universal across application frameworks and intermediaries. Never place a field in trailers when the recipient needs it before processing the content.
An HTTP/1.1 client can request a protocol change:
The server accepts with a 101 Switching Protocols response and the corresponding fields. Because the upgrade is connection-specific, an intermediary must understand and deliberately forward the handshake.
For a large upload, a client can send:
The server can examine the request fields before asking the client to send the content. This can avoid transmitting a large body that will be rejected for authentication, size, or policy reasons.
HTTP/2 and HTTP/3 manage streams and message boundaries in their own binary framing. They prohibit connection-specific fields such as:
TE is allowed only with the value trailers. A gateway translating from HTTP/1.1 must remove prohibited fields before creating an HTTP/2 or HTTP/3 message.
Persistent connections are the normal behavior in modern HTTP; a Keep-Alive header is not what enables them. Code that copies HTTP/1.1 headers wholesale into another protocol version risks protocol errors and security bugs.
Cross-Origin Resource Sharing, or CORS, is expressed through request and response fields. It tells a browser whether frontend code from one origin can access a response from another origin.
CORS is enforced by browsers. It is not authentication, and it does not prevent curl, a server process, or a malicious client from sending a request.
A browser identifies the calling origin:
The API can allow that origin:
If the server chooses the allowed origin dynamically, Vary: Origin prevents a shared cache from reusing one origin's response policy for another origin.
For a credentialed browser request, the response also needs:
When credentials are involved, Access-Control-Allow-Origin cannot be *; it must identify the permitted origin. The server must also configure its cookies and authentication policy consistently with the cross-site request.
For some cross-origin requests, the browser sends an OPTIONS preflight before the actual request:
The server describes what it allows:
The browser sends the PUT only if the preflight policy permits it. Access-Control-Max-Age lets the browser cache the preflight result for a limited time.
By default, browser scripts can read only a safelisted subset of response fields. A server can expose another response field:
Returning CORS fields does not grant application permissions. The server must authenticate and authorize the actual request exactly as it would for a non-browser client.
Browsers understand several response fields that reduce the impact of common web attacks.
HTTP Strict Transport Security tells a browser to use HTTPS for future connections to a host:
The browser honors this field only when it arrives over a secure connection. An attacker on an insecure connection must not be able to establish the policy.
max-age is measured in seconds. includeSubDomains extends the policy to subdomains and should be enabled only when all of them reliably support HTTPS. A mistaken long lifetime can make an unprepared site unreachable, so deployments should test with a shorter lifetime before increasing it.
Content Security Policy restricts which resources a document can load and where it can be embedded:
This example defaults resource loading to the same origin, disables plugins, prevents framing, and limits document base URLs. A real policy must match the application's resource usage. Copying an overly broad policy or immediately disabling a strict policy because it breaks the site provides little protection.
This field tells browsers not to reinterpret certain content as a different media type:
It works with an accurate Content-Type; it is not a substitute for sending the correct media type.
Referrer-Policy controls how much referrer information a browser sends:
This policy can send the full referrer for same-origin requests, send only the origin to another secure origin, and omit it when navigating from HTTPS to HTTP. Selecting a policy is a privacy and compatibility decision.
Security fields should be tested in the deployed response path. A field added by application code can be removed, duplicated, or overridden by a proxy, and a syntactically present policy can still be too permissive to help.
Distributed systems need a way to associate work across services. The W3C Trace Context format defines traceparent:
It carries a version, trace identifier, parent identifier, and trace flags in a portable format. A service validates it, creates a new parent identifier for its own operation, and forwards the resulting context to downstream calls.
tracestate can accompany it with vendor-specific trace state:
Many systems also use a non-standard correlation field such as:
That can be useful for log lookup, but it does not have universal semantics. The public edge should validate its format and length or generate a new value. Otherwise, a client can inject misleading or excessively large identifiers into logs.
Trace and request IDs are for correlation, not identity or authorization. They can also reveal internal information, so responses should expose only identifiers that operators are comfortable sharing with clients.
The historical X- prefix does not make a field private or safe. New protocols should use an existing standard field when one fits and document any application-specific field precisely.
The following request combines several field families:
Read it as a set of precise statements:
If the representation is unchanged, the server might respond:
There is no response content. The client reuses its stored representation and updates applicable metadata.
If the representation changed, the server could instead send:
Here, Content-Type describes the decoded representation, Content-Encoding describes its gzip coding, and Content-Length counts the 58 encoded bytes actually transmitted.
The Date field records when the message originated. A proxy can add fields such as Via and Age, but it should preserve the end-to-end representation metadata and remove connection-specific fields as required.
curl is often the quickest way to inspect an exchange.
Show request and response details:
-v writes protocol details to standard error and response content to standard output. Be careful when sharing the output because it can include cookies or authorization fields.
Fetch response headers without a response body:
This sends HEAD, which is not always handled exactly like GET by poorly configured servers. To inspect headers from a real GET while discarding its content:
Add a request field:
Ask for supported compression and let curl decode the response:
Browser developer tools show the logical fields exposed by the browser. They may normalize names, hide pseudo-headers, or label computed information as though it were a header. When exact wire behavior matters, compare observations at the client, edge proxy, and origin.
With HTTPS, a packet capture does not reveal HTTP fields unless the traffic is decrypted. Application logs, proxy diagnostics, or an explicitly configured development debugging proxy are usually more practical.
When debugging, inspect the entire path. A CDN might add Age, a gateway might rewrite Host, an ingress might create forwarding fields, and an application framework might compress content after application code computed a length.
They are case-insensitive. HTTP/2 and HTTP/3 encode them in lowercase. Code should not distinguish Content-Type from content-type.
Only fields whose grammar allows list combination can be safely joined. Set-Cookie is the most important exception and must remain as separate field lines.
It counts bytes in the message content. UTF-8 characters can occupy more than one byte, and compression changes the transmitted length.
Content-Encoding describes a transformation of the representation, such as gzip. Transfer-Encoding controls HTTP/1.1 message transfer and framing between peers.
It is just input unless a trusted proxy constructs it according to a known policy. Public clients can spoof forwarding fields.
no-cache permits storage but requires validation before reuse. no-store is the directive that asks caches not to store the message.
CORS controls whether browser frontend code can access a cross-origin response. It does not stop direct HTTP clients and does not replace authentication or authorization.
Secure restricts transport and HttpOnly restricts script access. Cookie scope, lifetime, cross-site behavior, server-side authorization, and application vulnerabilities still matter.
Both can be supplied by a client. They are useful metadata, not credentials.
HTTP/2 and HTTP/3 prohibit connection-specific fields. A translating intermediary must consume and remove them.
HTTP headers carry metadata and control information for requests, responses, and representations. Names are case-insensitive, values follow field-specific grammar, and repeated lines may be combined only when the field definition permits it.
Host and forwarding fields affect routing, but forwarding data is trustworthy only across controlled proxy boundaries. Content-Type, Content-Length, and Content-Encoding describe content; Accept negotiates representations, and Vary separates cached variants.
Authentication and cookies require protection from logging, broad scope, and insecure transport. ETag, If-None-Match, and If-Match support validation and concurrent updates. Cache-Control governs reuse: no-cache requires validation, while no-store prohibits storage.
Connection-specific fields belong to one hop and must not be forwarded blindly. CORS and browser security fields do not replace server authorization. Trace context and request IDs aid correlation only when validated and never treated as identity.
Interpret every header according to its defined scope, grammar, and trust boundary.
5 quizzes