An HTTP header is a name-value field sent alongside an HTTP request or response that carries metadata about the message: what format the body has, how it may be cached, who is asking, which credentials apply and how the connection should behave. Among Internet protocols, headers are the main extension mechanism of HTTP, because new behavior can be added by defining a new field without changing the protocol itself.
Headers are defined in RFC 9110 (HTTP semantics), with framing rules in RFC 9112 for HTTP/1.1, RFC 9113 for HTTP/2 and RFC 9114 for HTTP/3. Field names are case-insensitive, and a single message may carry dozens of them.
How HTTP headers are structured and transmitted
In HTTP/1.1, headers are plain text lines that follow the request line or status line, each written as Name: value and terminated by CRLF. An empty line marks the end of the header section, after which the optional body begins. A typical request carries Host, User-Agent, Accept and often Cookie.
HTTP/2 and HTTP/3 keep the same semantics but change the wire format. Field names are sent in lowercase, and the request line is replaced by pseudo-header fields such as :method, :path, :scheme and :authority. Header blocks are compressed with HPACK in HTTP/2 and QPACK in HTTP/3, which store repeated fields in a shared table and send only short references.
| Version | Encoding | Field name case |
|---|---|---|
| HTTP/1.1 | Plain text lines | Any case, compared case-insensitively |
| HTTP/2 | Binary frames with HPACK | Lowercase required |
| HTTP/3 | QUIC streams with QPACK | Lowercase required |
Some fields may appear several times; their values are then combined into a comma-separated list. The notable exception is Set-Cookie, which must remain separate because cookie values can contain commas. Fields that follow the body in chunked or framed messages are called trailers.
Main categories of HTTP headers
Headers are usually grouped by what part of the exchange they describe. The grouping is informal, but it helps when reading or debugging a message.
| Category | Representative fields | Purpose |
|---|---|---|
| Request context | Host, User-Agent, Referer | Identify target and client |
| Content negotiation | Accept, Accept-Language, Accept-Encoding | Ask for a preferred representation |
| Representation | Content-Type, Content-Length, Content-Encoding | Describe the body |
| Caching | Cache-Control, ETag, Last-Modified, Vary | Control storage and revalidation |
| Authentication | Authorization, WWW-Authenticate | Carry and request credentials |
| Security policy | Strict-Transport-Security, Content-Security-Policy | Instruct the browser to restrict behavior |
| Cross-origin access | Origin, Access-Control-Allow-Origin | Govern requests between sites |
Some fields only make sense in one direction. Host is mandatory in every HTTP/1.1 request, while Server, Location and Set-Cookie appear in responses. A redirect status code such as 301 or 302 is meaningless without a Location header naming the new URL.
Headers compared with other parts of an HTTP message
An HTTP message has three layers of information: the start line, the headers and the body. Placing data in the wrong layer is a frequent design mistake in web APIs.
| Part | Typical content | Visible in logs and proxies |
|---|---|---|
| Start line | HTTP method, path, status code | Almost always |
| Headers | Metadata, credentials, caching rules | Often, depending on configuration |
| Body | The resource or submitted data | Rarely |
| Query string | Parameters of the URL | Almost always |
Credentials belong in the Authorization header rather than the URL, because query strings end up in browser history, server logs and Referer values. Conversely, data that identifies the resource belongs in the path, since many caches use the URL, not custom headers, as the primary cache key.
Practical limits and configuration choices
The specification sets no fixed size limit, but servers and proxies do. Common defaults allow about 8 KB to 16 KB for a single field and roughly 32 KB to 64 KB for the whole header section; exceeding them produces a 431 or 400 status code. Oversized cookies are the most frequent cause.
Custom headers were once marked with an X- prefix, such as X-Forwarded-For. That convention was deprecated by RFC 6648, and new fields should use descriptive names without the prefix; the standardized Forwarded header now covers the proxy case.
Intermediaries treat fields differently depending on their scope. End-to-end headers such as Content-Type or Cache-Control must be forwarded unchanged to the final recipient. Hop-by-hop headers such as Connection, Keep-Alive, Transfer-Encoding and Upgrade apply only to a single connection, and every proxy removes them before passing the message on. HTTP/2 and HTTP/3 forbid connection-specific fields altogether, so a server that still emits them can trigger protocol errors in strict clients.
The order of different fields carries no meaning in the specification, yet repeated fields with the same name must keep their relative order, because combining them changes the resulting list. Applications should therefore read headers through a library that normalizes names and handles repetition correctly.
- Send security headers such as HSTS and Content-Security-Policy from every HTML response.
- Use Vary whenever a response changes with Accept-Encoding, Accept-Language or cookies.
- Remove version-revealing fields such as detailed Server or X-Powered-By values.
- Never trust client-supplied forwarding headers unless a known proxy sets them.
Common pitfalls with HTTP headers
Header bugs are often invisible in a browser and appear only behind a CDN, a load balancer or a strict client. The table summarizes frequent symptoms and their causes.
| Symptom | Likely cause | Fix |
|---|---|---|
| Wrong language or format served from cache | Missing Vary field | Add Vary for the negotiated header |
| Request rejected with 431 | Cookies too large | Trim cookies, move state server-side |
| Spoofed client IP in logs | Trusting X-Forwarded-For blindly | Accept it only from trusted proxies |
| Response splitting | Unescaped CR or LF in a header value | Validate and encode values |
| Cross-origin call blocked | Missing or wrong CORS response fields | Return correct Access-Control headers |
In simple terms
When a browser asks a server for a page, the request is like a parcel with a label stuck on the outside. HTTP headers are the lines on that label: who is sending it, what language the reader prefers, and whether a stored copy is acceptable.
The server answers with its own label. It says what kind of content is inside, how long it may be kept and which security rules the browser must follow. The content itself travels separately, in the body.
A practical example
A news site serves articles in English and German from the same URL, choosing the language from the browser’s Accept-Language header. After a CDN is added in front of the site, German readers begin to see English pages. The engineers inspect the responses and find that no Vary header is sent, so the CDN stores one copy per URL and serves it to everyone.
They add Vary: Accept-Language and set Cache-Control: public, max-age=300 on article pages. The CDN now keeps separate cached copies per language, the mix-up disappears within five minutes, and the origin load stays low because each language version is still cached at the edge.