What role do HTTP headers play in requests and responses?
HTTP headers carry metadata about the request and response: who's sending it (User-Agent), what content is acceptable (Accept, Accept-Language), auth (Authorization, Cookie), caching directives (Cache-Control, ETag, If-None-Match), content negotiation (Content-Type, Content-Encoding), CORS (Origin, Access-Control-Allow-*), security (CSP, HSTS, X-Frame-Options), and tracing. Headers steer routing, caching, security policy, and content selection without touching the body.
HTTP headers are key/value metadata sent alongside a request or response body. They control how the request is interpreted and what the response should look like, without changing the actual payload.
Categories of headers
Request — telling the server what you want
| Header | Purpose |
|---|---|
Host | Which virtual host (required in HTTP/1.1). |
User-Agent | Client identity (browser, OS, app). |
Accept | Media types the client can handle (application/json, /). |
Accept-Language | Preferred languages (used for i18n). |
Accept-Encoding | Compression (gzip, br). |
Authorization | Credentials (Bearer <jwt>, Basic …). |
Cookie | Client-side session/state. |
Origin | Originating scheme+host (CORS). |
Referer | The page that triggered the request. |
If-None-Match | Conditional request — only send body if ETag changed. |
If-Modified-Since | Conditional — only send if newer than this date. |
Response — describing what the server sent
| Header | Purpose |
|---|---|
Content-Type | MIME type + charset (application/json; charset=utf-8). |
Content-Length | Body size in bytes. |
Content-Encoding | Compression (gzip, br). |
Cache-Control | Caching directives (max-age=3600, no-store, s-maxage). |
ETag | Opaque version token for conditional requests. |
Last-Modified | Body's last modification timestamp. |
Set-Cookie | Establish a cookie (with HttpOnly, Secure, SameSite). |
Location | Redirect target (3xx) or new resource (201). |
Access-Control-Allow-* | CORS policy. |
Strict-Transport-Security | Force HTTPS for future requests (HSTS). |
Content-Security-Policy | Restrict script/style/iframe sources (XSS defense). |
X-Frame-Options | Block iframe embedding (clickjacking defense). |
Common patterns
Conditional GET (saves bandwidth on cache validation):
- Server responds with
ETag: "abc123". - Browser caches body + ETag.
- Next request sends
If-None-Match: "abc123". - Server compares; if unchanged, returns
304 Not Modified(empty body).
Content negotiation:
GET /article/42
Accept: application/json
Accept-Language: frServer returns French JSON. Same URL, different representation per request.
CORS:
Browser sends Origin: https://app.example.com. Server responds with Access-Control-Allow-Origin: https://app.example.com. Without that header, the browser blocks the response from script access. Preflight OPTIONS requests negotiate non-simple requests in advance.
Authentication:
Authorization: Bearer eyJhbGciOi… ← JWTOr Cookie: session=abc for cookie-based sessions. Stateless JWT vs stateful cookie is a separate decision; the header is the carrier.
Compression:
Accept-Encoding: gzip, brServer picks the best supported and responds with Content-Encoding: br. Bodies shrink ~70%.
Custom headers
X-Request-Id, X-Trace-Id, X-Tenant-Id, etc. — application-specific metadata. Prefix with X- is legacy; modern advice is to use plain names (Request-Id).
Security implications
Set-CookiewithoutHttpOnly→ readable from JS → XSS vector.- Missing
Content-Security-Policy→ no defense-in-depth against script injection. - Missing
X-Content-Type-Options: nosniff→ MIME sniffing can execute HTML as script. - Permissive
Access-Control-Allow-Origin: *with credentials → cross-origin data leak.
In the browser
fetch('/api/user', {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
},
});A few headers are forbidden — set automatically by the browser and uneditable from JS: Host, Cookie, Referer, User-Agent (mostly). Others are CORS-safelisted (Accept, Content-Language); setting anything else triggers a preflight.
Follow-up questions
- •What's the difference between Cache-Control and ETag?
- •Why are some headers forbidden from being set by JS?
- •How does the SameSite cookie attribute defend against CSRF?
- •What's the role of Vary in caching responses?
Common mistakes
- •Setting Authorization in a service worker fetch and forgetting credentials: 'include' for cookies.
- •Missing Cache-Control on API responses — defaults to heuristic caching, which can serve stale data.
- •CORS misconfig: Access-Control-Allow-Origin: * with Allow-Credentials: true (browsers reject this).
- •Trusting the User-Agent header for security decisions — trivially spoofable.
- •Logging Authorization headers in server logs — token leakage.
- •Setting Content-Type: application/json but sending a string body that isn't valid JSON.
Performance considerations
- •Headers ship on every request — HTTP/1.1 has no header compression, so verbose headers (large cookies, big JWTs) cost real bytes per request. HTTP/2 and HTTP/3 use HPACK/QPACK header compression, which makes repeated headers nearly free across a connection. Big wins: Cache-Control / ETag (skip server work and bytes), Accept-Encoding (compressed responses), Content-Encoding: br (best ratio).
Edge cases
- •HTTP/2 lowercases header names; case-insensitive comparison is required.
- •Header values are 7-bit ASCII by default; non-ASCII (filenames, etc.) need RFC 5987 encoding.
- •Total header size has implementation limits (~8KB per header, ~64KB total) — large JWTs can blow this.
- •Set-Cookie can appear multiple times in one response; other headers can be folded with commas but support varies.
- •Cache-Control: no-store vs no-cache are different — no-cache *does* cache but revalidates each time.
Real-world examples
- •Cloudflare and other CDNs key cache lookups on URL + Vary headers; getting Vary right is critical.
- •GitHub API uses ETag + If-None-Match aggressively; cached calls don't count against rate limit.
- •OpenAI and other LLM APIs use Authorization: Bearer for API keys.