Every HTTP request and response carries a set of headers alongside the actual content. Most developers know they exist, but few take the time to understand what each one actually does. That gap causes real problems: broken caching, failed authentication, confusing CORS errors, security misconfigurations, and content that renders incorrectly in certain clients.
HTTP headers are key-value pairs sent before the body of any request or response. They tell the browser, server, and any intermediaries how to handle what they're about to receive. This guide covers every category of header you'll regularly encounter, what they do, and when you need to care about them.
How to Read HTTP Headers
The fastest way to inspect headers is your browser's DevTools. Open the Network tab, click any request, and you'll see two sections: Request Headers (what your browser sent) and Response Headers (what the server sent back). For API work, the cURL Generator on DevToolShack lets you build a command with -I (fetch headers only) or -v (verbose, shows full request and response headers) without having to remember the flags.
Headers are case-insensitive by the HTTP spec, though they're conventionally written in Title-Case. Values are case-sensitive. A header that looks like this in a raw response:
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=3600, public
X-Content-Type-Options: nosniff
...is delivering three separate pieces of information: what format the body is in, how long it can be cached, and an instruction to the browser not to sniff the content type.
Request Headers
Request headers travel from client to server. They describe the client, what it's asking for, and any credentials it's presenting.
Content-Type
Tells the server what format the request body is in. When you POST JSON to an API, you need Content-Type: application/json. When submitting an HTML form, the browser sends application/x-www-form-urlencoded by default, or multipart/form-data for file uploads. If this header is missing or wrong, many servers will reject the request or misparse the body.
Accept
Tells the server what content types the client can handle in the response. A browser typically sends something like Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8. An API client might send Accept: application/json. The q= values represent preference weights. Servers use this for content negotiation, serving JSON to API clients and HTML to browsers from the same endpoint.
Authorization
Carries credentials for the server to authenticate the request. The two most common schemes are Basic (Base64-encoded username:password, only safe over HTTPS) and Bearer (a token, most often a JWT). The header looks like Authorization: Bearer eyJhbGci.... This header is one reason HTTPS is non-negotiable: over plain HTTP, credentials travel in the clear.
User-Agent
Identifies the client software. Browsers send long strings describing the browser, OS, and rendering engine. Crawlers, curl, and custom API clients send their own strings. Servers sometimes use User-Agent for analytics, bot detection, or serving different content to different clients. It can be spoofed trivially, so it's unreliable for anything security-critical.
Cookie
Sends stored cookies back to the server with every matching request. The browser manages this automatically. Session tokens, preference flags, and tracking identifiers all travel this way. The server sets cookies via the Set-Cookie response header; the browser stores them and returns them on subsequent requests.
Origin and Referer
Origin identifies the origin (scheme + host + port) that initiated the request and is central to how CORS works. Referer (note the historical misspelling) carries the full URL of the page that triggered the request. Both are sent automatically by browsers and are used by servers for analytics, CSRF protection, and access control decisions.
Response Headers
Response headers travel from server to client. They describe what's being sent and instruct the client on how to handle it.
Content-Type
The response version tells the browser what format the body is in. Content-Type: text/html; charset=utf-8 tells the browser to parse HTML and treat the text as UTF-8. application/json tells it to expect JSON. If a server returns HTML with Content-Type: text/plain, the browser will display it as raw text rather than rendering it. Getting this header wrong is one of the most common causes of pages that display source code instead of rendered content.
Cache-Control
One of the most important and most misunderstood headers. It controls whether the response can be cached, by whom, and for how long. Key directives:
max-age=N— cache for N secondsno-cache— cache the response but revalidate with the server before each useno-store— don't cache at all (use for sensitive data)public— CDNs and shared caches can store itprivate— only the end user's browser should cache it (not CDNs)immutable— the resource will never change; don't revalidate even on refresh
A good caching strategy for versioned static assets (JS, CSS with hashed filenames) is Cache-Control: max-age=31536000, immutable. For HTML pages you want cached but always fresh: Cache-Control: no-cache paired with an ETag.
ETag and Last-Modified
These two headers support conditional requests, which let the browser revalidate a cached resource without downloading it again if nothing changed. ETag is a unique identifier (usually a hash) for a specific version of a resource. Last-Modified is a timestamp. On the next request, the browser sends If-None-Match with the ETag or If-Modified-Since with the timestamp. If nothing has changed, the server returns a 304 Not Modified with no body, saving bandwidth.
Location
Used with redirect status codes (301, 302, 307, 308) to tell the client where to go. When a server responds with 301 Moved Permanently and Location: https://newsite.com/page, the browser automatically follows it. For a deeper look at status codes and what each redirect type means, see How to Read and Debug HTTP Status Codes.
Set-Cookie
Instructs the browser to store a cookie. Important attributes:
HttpOnly— the cookie is inaccessible to JavaScript; protects against XSS stealing session tokensSecure— the cookie is only sent over HTTPSSameSite=Strict|Lax|None— controls whether the cookie is sent on cross-site requests, which affects CSRF exposureMax-AgeorExpires— when the cookie should expire
document.cookie and exfiltrate it.Security Headers
Security headers are response headers that instruct the browser to enforce specific protections. They're one of the cheapest, highest-impact security improvements you can make to any web application. None of them require code changes — you configure them at the server or CDN level.
Content-Security-Policy (CSP)
The most powerful security header. It tells the browser which sources it's allowed to load scripts, styles, images, fonts, and frames from. A strict CSP prevents cross-site scripting (XSS) attacks by blocking scripts that weren't explicitly permitted. A basic policy that only allows resources from your own domain looks like:
Content-Security-Policy: default-src 'self'
In practice, most sites need to allow specific CDNs, analytics, and font providers, so real policies are more complex. You can start with Content-Security-Policy-Report-Only to test a policy without enforcing it.
Strict-Transport-Security (HSTS)
Tells browsers to only connect to your site over HTTPS, even if the user types http:// or follows an HTTP link. Once a browser sees this header, it will upgrade future requests automatically for the duration of max-age. The includeSubDomains directive extends this to all subdomains. Adding preload and submitting to the HSTS preload list bakes this into browsers before they've ever visited your site.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options
Controls whether your page can be embedded in an <iframe>. DENY blocks all framing; SAMEORIGIN allows framing only from the same origin. This prevents clickjacking attacks. CSP's frame-ancestors directive is the modern replacement, but X-Frame-Options remains widely used for backwards compatibility.
X-Content-Type-Options
Set to nosniff, this tells the browser not to guess the content type if the Content-Type header is set. Without it, browsers sometimes "sniff" binary content and decide it looks like HTML, which can lead to script execution from uploaded files. It's a single-value header: X-Content-Type-Options: nosniff.
Referrer-Policy
Controls how much of the referring URL is included in the Referer header on outgoing requests. no-referrer sends nothing; same-origin sends the full URL for same-origin requests but nothing for cross-origin; strict-origin-when-cross-origin is a sensible default that sends origin only for cross-origin HTTPS requests.
Permissions-Policy
Formerly Feature-Policy. Lets you restrict access to browser APIs and hardware features (camera, microphone, geolocation, payment, etc.) for your page and any embedded iframes. If your app doesn't use the camera, blocking it here means a compromised third-party script can't access it either.
CORS Headers
CORS headers are a subset of response headers that tell the browser whether a cross-origin request is permitted. They're set by the server to explicitly grant permission to specific origins. The key headers are Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, and Access-Control-Allow-Credentials. For a thorough explanation of how preflight requests work and how to fix every CORS error, see What Is CORS and How Do You Fix It?
Caching and Compression Headers
Vary
Tells caches that the response may differ based on specific request headers. Vary: Accept-Encoding means the cached response for gzip-accepting clients is different from the one for clients that don't support it. If you have a header that drives content negotiation (language, encoding, device type), that header should appear in Vary, or caches will serve the wrong version to the wrong clients.
Content-Encoding
Tells the client that the response body has been compressed. Content-Encoding: gzip or Content-Encoding: br (Brotli) are the common values. The browser decompresses transparently. You'll see compression savings reflected in your DevTools Network tab in the "Size" versus "Transferred" columns.
Transfer-Encoding
Transfer-Encoding: chunked means the response body is being sent in pieces rather than all at once, which is useful when the server doesn't know the total content length in advance — common for streaming responses and server-sent events.
Custom and Vendor Headers
Any header starting with X- is conventionally a custom or vendor-specific header, though that convention has been formally deprecated. Common examples:
X-Request-ID— a unique ID for tracing a request through distributed systemsX-RateLimit-Remaining— how many API calls are left in the current windowX-Forwarded-For— the original client IP when the request has passed through a proxy or load balancerX-Powered-By— the server technology (often suppressed for security since it advertises your stack)
curl -I https://example.com to fetch only headers, or curl -v https://example.com to see both request and response headers in full. The cURL Generator lets you build these commands visually without memorizing flags.Practical Checklist: Headers Every Production Site Should Have
If you're auditing an existing site or launching a new one, run through this list:
- Strict-Transport-Security — enforce HTTPS at the browser level
- Content-Security-Policy — restrict resource loading to trusted sources
- X-Frame-Options — prevent clickjacking
- X-Content-Type-Options: nosniff — prevent MIME type sniffing
- Referrer-Policy — control referrer data leakage
- Cache-Control — set appropriate caching for each resource type
- Content-Type with charset — prevent charset sniffing on text resources
You can configure most of these in a single .htaccess block on Apache or in a location block in nginx — no application code required. The .htaccess Generator on DevToolShack can produce the relevant directives for Apache setups.