Understanding HTTP Headers: A Developer's Reference

Understanding HTTP Headers: A Developer's Reference

Request Headers vs Response Headers

Every HTTP transaction involves two sets of headers: the ones your client sends with the request, and the ones the server sends back. They serve different purposes, and knowing which does what saves time when something isn't working.

Request headers give the server context about who's asking and what they want. Response headers tell the client what it got back, how to handle it, and what it's allowed to do with it. Request headers travel up, response headers travel down, and both sides read each other's metadata before processing the body.

Headers are just text: Name: value\r\n, one per line, case-insensitive names by convention (though HTTP/2 lowercases them all). Understanding the key ones is genuinely useful day-to-day, not just for debugging.

The Most Important Request Headers

Content-Type tells the server what format the request body is in. If you're sending JSON, it should be application/json. For form submissions, application/x-www-form-urlencoded or multipart/form-data. Getting this wrong is one of the most common causes of a backend silently receiving empty or malformed data.

POST /api/users HTTP/1.1
Content-Type: application/json

{"name": "Alice", "email": "alice@example.com"}

Accept tells the server what response formats the client can handle. Most APIs accept this gracefully and respond with Content-Type: application/json regardless, but some REST APIs will content-negotiate and return XML if you ask for it.

Authorization carries credentials. The two most common formats are Bearer tokens (JWTs, OAuth access tokens) and Basic auth (base64-encoded username:password). More on both below.

Cookie sends stored cookies back to the server with every request in scope. Session cookies, auth tokens, tracking identifiers — they all ride in this header. The browser manages this automatically; you rarely need to set it manually unless you're writing tests or a non-browser client.

User-Agent identifies the client. Browsers send verbose strings like Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 .... Your app can send whatever makes sense: MyApp/2.1.0, curl/8.4.0, etc. Some servers make decisions based on this — serve different content to bots, throttle certain clients.

Origin is sent on cross-origin requests and is the foundation of CORS. It tells the server where the request originated: Origin: https://yourapp.com. The server uses this to decide whether to allow the request. Unlike Referer, Origin only includes the scheme, host, and port — no path.

The Most Important Response Headers

Content-Type in a response tells the client how to interpret the body. application/json, text/html; charset=utf-8, image/webp, application/pdf. The ;charset=utf-8 suffix matters. Without it, some browsers default to ISO-8859-1 for text/* types, which mangles anything outside the ASCII range.

Cache-Control controls caching at every level (browser, CDN, reverse proxy). The values compose:

Cache-Control: public, max-age=31536000, immutable  # long-lived static asset
Cache-Control: no-cache                              # revalidate every time
Cache-Control: no-store                              # never cache (auth pages)
Cache-Control: private, max-age=300                 # browser only, 5 min

Set-Cookie creates or updates a cookie. The flags on this header are critical for security: HttpOnly prevents JavaScript from reading it (protects against XSS), Secure restricts it to HTTPS, and SameSite=Lax or SameSite=Strict prevents it from being sent on cross-site requests (protects against CSRF).

Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax

Strict-Transport-Security (HSTS) tells browsers to only connect over HTTPS for a specified duration. Once a browser has seen this header, it won't allow HTTP connections to the host — not even if the user types http://. The includeSubDomains flag extends this to all subdomains.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

X-Content-Type-Options: nosniff tells the browser not to MIME-sniff the response — trust the Content-Type header exactly. Without it, browsers try to auto-detect content type, which can lead to script injection if an attacker uploads a file that looks like JavaScript.

CORS headers are the response side of cross-origin resource sharing. For requests that trigger a preflight (non-simple methods or custom headers), the browser first sends an OPTIONS request and checks these headers before sending the actual request:

Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

Access-Control-Allow-Origin: * means any origin can access the resource — fine for public APIs, wrong for anything that uses cookies or per-user data.

Authorization Header Formats

Three patterns you'll encounter regularly:

Basic auth encodes username:password in Base64 and sends it on every request. Simple, stateless, and fine over HTTPS. Never over plain HTTP.

Authorization: Basic dXNlcjpwYXNzd29yZA==

The encoded value is just user:password through Base64 — not encrypted, just encoded. You can verify or generate these with the Base64 Encoder.

Bearer tokens are the modern standard. The token is opaque to the client — it might be a JWT, an OAuth access token, or a random string that maps to a session in a database.

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

API keys are often sent in a custom header rather than Authorization, though there's no universal convention:

X-API-Key: my-secret-api-key
Authorization: ApiKey my-secret-api-key

When you're working with raw JSON responses from APIs and need to inspect authorization payloads or token structures, JSON Formatter makes decoded JWT payloads much easier to read.

Inspecting Headers in DevTools

Chrome and Firefox both make this easy. Open DevTools with F12 or Cmd+Option+I, go to the Network tab, click any request, and look at the Headers panel.

The Headers tab shows both request and response headers. The Preview and Response tabs show the body. For XHR / fetch requests specifically, filter by Fetch/XHR to reduce noise.

For APIs and scripting, curl -v prints full headers:

curl -v https://api.example.com/users \
  -H "Authorization: Bearer my-token" \
  -H "Accept: application/json"

The lines prefixed with > are request headers; < are response headers. Or use curl -I for a HEAD request to see response headers without downloading the body.

If you're decoding URL-encoded header values — authentication challenges, redirect locations, encoded cookie values — URL Encoder handles both encoding and decoding in the browser.

Common Header Debugging Scenarios

CORS errors almost always surface in the browser console as blocked by CORS policy. Check Access-Control-Allow-Origin in the response (or its absence) and the Origin in the request. If the server doesn't return the right Allow-Origin value, the browser blocks the response even if it received HTTP 200.

401 Unauthorized means the request lacked valid credentials — check your Authorization header format and token validity. 403 Forbidden means credentials were valid but the user doesn't have permission. It's an authorization problem, not an authentication one. They're not interchangeable.

Missing Content-Type on POST requests causes a large percentage of "my API is receiving empty data" bugs. If you're sending a JSON body, you need Content-Type: application/json on the request — full stop.

For a deeper look at the caching side, see How HTTP Caching Works. For the broader HTTP status code reference, HTTP Status Codes: A Developer's Guide covers what each range means and when to use which code.

The MDN HTTP headers reference is the most complete resource for anything beyond what's covered here — every header has a full specification page with browser compatibility tables and security notes.