
HTTP Headers Explained: The Hidden Metadata That Controls Every Web Page
What Are HTTP Headers?
HTTP headers are the invisible metadata that accompanies every single request and response transmitted across the web. When you type a URL into your browser and press Enter, your browser does not simply ask for a page — it sends a structured HTTP request that includes a request line (method, URL, HTTP version) followed by a series of key-value pairs called headers. These headers tell the server who you are, what you want, what formats you accept, what languages you prefer, and a wealth of other contextual information. The server responds with its own set of headers that instruct your browser how to handle the content, whether to cache it, how long it is valid, and what security policies apply. Understanding HTTP headers is essential for anyone working with web technologies, from frontend developers debugging CORS errors to security engineers hardening web applications against exploitation.
The HTTP/1.1 specification (RFC 7230-7235, now superseded by RFC 9110-9114) defines headers as flexible, extensible name-value pairs separated by a colon, terminated by a carriage return and line feed (CRLF). A typical HTTP request includes between 5 and 20 headers, while responses commonly carry 10 to 30 or more. The total size of headers in a request can range from a few hundred bytes to several kilobytes, and in pathological cases — such as cookies carrying large session tokens — headers alone can exceed 8 KB. With HTTP/2, headers are compressed using HPACK encoding to reduce this overhead, but the logical structure remains the same. Every header belongs to a category: request headers sent by the client, response headers sent by the server, representation headers that describe the message body, and payload headers that describe transfer encoding.
Headers are arguably the most important control plane of the web. They govern caching behavior (determining whether your browser re-downloads a resource or uses a stored copy), security posture (through headers like Content-Security-Policy, Strict-Transport-Security, and X-Frame-Options), content negotiation (allowing clients and servers to agree on language, encoding, and media type), authentication (carrying tokens and credentials), and cross-origin access control (through the CORS header family). A misconfigured header can break functionality, degrade performance, or create critical security vulnerabilities. According to the OWASP Top 10, security misconfiguration — which frequently involves missing or incorrect HTTP headers — remains one of the most prevalent web application security risks. This guide provides a comprehensive reference for understanding, configuring, and debugging HTTP headers in modern web applications.
Request Headers vs Response Headers
HTTP headers flow in both directions of the client-server communication, and understanding the distinction between request headers and response headers is fundamental to mastering web protocols. Request headers are sent by the client (typically a browser) to the server as part of an HTTP request message. They communicate the client's capabilities, preferences, and context — telling the server what content types it can process, what languages it prefers, what encoding it supports, and what authentication credentials it possesses. Response headers are sent by the server back to the client as part of an HTTP response message. They communicate the server's instructions, metadata about the response body, caching directives, security policies, and session management information.
The asymmetry between request and response headers is significant. Request headers are primarily informational and advisory — they tell the server what the client wants and can handle, but the server is not obligated to honor every request header. For example, a client may send Accept-Encoding: gzip, br to indicate it supports compressed responses, but the server may choose to send uncompressed content anyway. Response headers, by contrast, are often directive and authoritative — they instruct the client how to behave. A Strict-Transport-Security response header compels the browser to use HTTPS for all future requests to that domain. A Content-Security-Policy header restricts what resources the browser is allowed to load. These response headers are security-critical because they shift trust decisions from the application layer to the browser's enforcement engine.
Some headers can appear in both requests and responses but serve different purposes depending on the direction. The Connection header, for instance, can be sent by either party to control whether the TCP connection remains open after the current transaction. The Content-Type header appears in both directions: in requests with a body (like POST), it tells the server what format the request body is in; in responses, it tells the client what format the response body is in. Understanding these dual-use headers requires attention to context and direction. The following comparison panel summarizes the key differences between request and response headers.
The Most Important HTTP Headers
Hundreds of HTTP headers are defined across various RFCs, vendor specifications, and de facto standards, but a relatively small subset accounts for the vast majority of headers you will encounter in everyday web development and operations. The following reference table catalogs the most important headers, their direction, purpose, and the RFC or specification that defines them. This is not an exhaustive list — the IANA HTTP Header Field Registry contains over 250 entries — but it covers the headers that every web professional should understand.
| Header | Direction | Purpose | Specification |
|---|---|---|---|
| Host | Request | Specifies the target host and port (required in HTTP/1.1) | RFC 9110 |
| User-Agent | Request | Identifies the client software making the request | RFC 9110 |
| Accept | Request | Declares MIME types the client can process | RFC 9110 |
| Accept-Language | Request | Declares preferred natural languages | RFC 9110 |
| Accept-Encoding | Request | Declares supported content codings (gzip, br, zstd) | RFC 9110 |
| Authorization | Request | Contains credentials for authenticating the client | RFC 9110 |
| Cookie | Request | Sends stored cookies to the server | RFC 6265 |
| Referer | Request | Indicates the URL of the page that linked to the request | RFC 9110 |
| If-Modified-Since | Request | Makes a request conditional on modification time | RFC 9110 |
| If-None-Match | Request | Makes a request conditional on ETag value | RFC 9110 |
| Content-Type | Both | Indicates the MIME type of the message body | RFC 9110 |
| Content-Length | Both | Indicates the size of the message body in bytes | RFC 9110 |
| Content-Encoding | Response | Specifies the encoding applied to the response body | RFC 9110 |
| Set-Cookie | Response | Writes a cookie to the client's cookie store | RFC 6265 |
| Cache-Control | Both | Directives for caching mechanisms | RFC 9111 |
| ETag | Response | Opaque identifier for a specific version of a resource | RFC 9110 |
| Location | Response | Indicates the target URL for redirects | RFC 9110 |
| Server | Response | Identifies the server software handling the request | RFC 9110 |
| X-Request-ID | Both | Correlation ID for distributed request tracing | De facto standard |
Several of these headers deserve additional commentary. The Host header is the only mandatory request header in HTTP/1.1 — without it, the server cannot determine which virtual host should handle the request, because multiple domains may share a single IP address. The Authorization header carries credentials using schemes like Bearer tokens (RFC 6750) for OAuth 2.0, Basic authentication (base64-encoded username:password), or Digest authentication. The Referer header (note the historical misspelling) is a privacy concern because it can leak sensitive URL parameters; the Referrer-Policy response header was introduced to control how much referrer information is shared. The ETag header enables conditional requests that dramatically reduce bandwidth by allowing clients to verify whether a cached resource is still valid without re-downloading it.
Security Headers Every Website Must Have
Security headers are HTTP response headers that instruct browsers to activate built-in protective mechanisms, creating a defense-in-depth layer that mitigates common web vulnerabilities even when application-level defenses fail. Despite their proven effectiveness — Scott Helme's annual scan of the top 1 million websites consistently shows that sites using security headers have significantly lower rates of XSS, clickjacking, and MITM attacks — adoption remains alarmingly low. As of 2025, only about 30% of the top 1 million sites implement Content-Security-Policy, and fewer than 40% use Strict-Transport-Security. Every website, regardless of size or complexity, should implement the following security headers to protect its users.
Strict-Transport-Security (HSTS)
Strict-Transport-Security (HSTS) instructs the browser to only connect via HTTPS for the specified duration, eliminating the possibility of SSL-stripping attacks where a man-in-the-middle downgrades the connection from HTTPS to HTTP. The includeSubDomains directive extends this protection to all subdomains, and the preload directive allows the domain to be included in browser-maintained HSTS preload lists that enforce HTTPS from the very first visit. Without HSTS, even a site that redirects HTTP to HTTPS is vulnerable during the initial redirect — an attacker can intercept this redirect and keep the victim on an HTTP connection. HSTS should be deployed with a max-age of at least 31536000 seconds (1 year), and sites should submit to the HSTS preload list after testing.
Content-Security-Policy (CSP)
Content-Security-Policy (CSP) is the most powerful security header, allowing you to specify precisely which sources of content the browser is allowed to load for a given page. A well-crafted CSP prevents cross-site scripting (XSS) by blocking inline scripts and restricting script sources to trusted origins, prevents data exfiltration by controlling which domains can receive data via img, form, and connect directives, and prevents clickjacking by restricting frame-ancestors. However, CSP is also the most complex header to implement correctly — an overly permissive policy provides no real protection, while an overly restrictive policy breaks functionality. Start with a report-only mode (Content-Security-Policy-Report-Only) to identify violations before enforcement, then gradually tighten the policy.
X-Content-Type-Options
X-Content-Type-Options: nosniff prevents MIME-type sniffing, a browser behavior where the browser guesses the content type of a response instead of respecting the declared Content-Type. Without this header, an attacker who finds a way to upload a file containing executable content (like HTML with JavaScript) to your server could trick the browser into executing it, even if the Content-Type header says it is an image or text file. This simple one-value header eliminates an entire class of content injection attacks.
X-Frame-Options
X-Frame-Options prevents your page from being embedded in iframe elements on other domains, which is the primary mechanism for clickjacking attacks. Although the CSP frame-ancestors directive is the modern replacement, X-Frame-Options should still be included for backward compatibility with older browsers. Use DENY to prevent all framing, or SAMEORIGIN to allow framing only by pages on the same domain.
Permissions-Policy
Permissions-Policy (formerly Feature-Policy) allows you to selectively enable or disable browser features and APIs on your site, such as the camera, microphone, geolocation, fullscreen mode, and USB access. By restricting features to only the origins that need them, you reduce the attack surface available to compromised scripts and reduce the risk of unauthorized data access through exploited browser APIs.
| Security Header | Purpose | Risk Without It | Risk Level | Recommended Value |
|---|---|---|---|---|
| Strict-Transport-Security | Force HTTPS connections | SSL stripping, MITM attacks | Critical | max-age=31536000; includeSubDomains; preload |
| Content-Security-Policy | Control resource loading | XSS, data exfiltration, injection | Critical | default-src 'self'; script-src 'self'; style-src 'self' |
| X-Content-Type-Options | Prevent MIME sniffing | Content type confusion attacks | High | nosniff |
| X-Frame-Options | Prevent clickjacking | UI redress attacks | High | DENY or SAMEORIGIN |
| Referrer-Policy | Control referrer leakage | URL parameter leaks to third parties | High | strict-origin-when-cross-origin |
| Permissions-Policy | Restrict browser APIs | Unauthorized API access | Medium | camera=(), microphone=(), geolocation=(self) |
| Cross-Origin-Opener-Policy | Isolate browsing context | Cross-origin information leaks | Medium | same-origin |
| Cross-Origin-Resource-Policy | Control cross-origin reads | Cross-origin data theft via Spectre | Medium | same-origin |
CORS Headers
Cross-Origin Resource Sharing (CORS) is the mechanism that allows web applications running at one origin to request resources from a different origin. By default, browsers enforce the Same-Origin Policy (SOP), which prevents a script on origin A from reading responses from origin B. This is a critical security boundary — without SOP, any website you visit could read your private data from any other website you are logged into. However, the modern web is inherently cross-origin: frontend applications hosted on one domain need to call APIs on another, CDNs serve static assets from different domains, and web fonts are loaded from specialized type foundries. CORS provides a controlled, opt-in mechanism for relaxing the Same-Origin Policy where it is necessary, while preserving it where it is not.
The CORS protocol works through a set of HTTP headers that the server includes in its responses to indicate which origins, methods, and headers are permitted for cross-origin requests. For simple requests (GET, HEAD, or POST with standard content types), the browser adds an Origin request header, and the server responds with Access-Control-Allow-Origin to grant or deny access. For "preflighted" requests — those using methods other than GET/HEAD/POST, or those with custom headers — the browser first sends an OPTIONS request to check whether the actual request is allowed. The server responds to the preflight with headers indicating the permitted methods, headers, and credentials, and only if the preflight passes does the browser send the actual request. This two-step process protects against cross-origin requests that could modify data on the server.
The key CORS headers include Access-Control-Allow-Origin (specifies which origins are permitted — never use the wildcard * for credential-bearing requests), Access-Control-Allow-Methods (lists allowed HTTP methods), Access-Control-Allow-Headers (lists allowed request headers), Access-Control-Allow-Credentials (indicates whether cookies and authorization headers can be included in cross-origin requests), Access-Control-Max-Age (specifies how long the preflight result can be cached), Access-Control-Expose-Headers (lists response headers the browser should make available to JavaScript), and Access-Control-Request-Method/Access-Control-Request-Headers (used in preflight requests to declare the intended method and headers).
Common CORS misconfigurations create severe security vulnerabilities. Setting Access-Control-Allow-Origin to * when credentials are involved causes the browser to reject the response entirely — but some servers work around this by reflecting the request's Origin header directly into the response, which effectively allows any origin to make authenticated cross-origin requests. This "Origin reflection" vulnerability is one of the most dangerous CORS misconfigurations, as it completely defeats the Same-Origin Policy. Always explicitly enumerate the origins you trust, and never reflect the Origin header without validation. Use a server-side allowlist of permitted origins, and only set Access-Control-Allow-Origin to values from that allowlist.
Caching Headers
HTTP caching is one of the most impactful yet poorly understood aspects of web performance. Properly configured caching can reduce server load by 70-90%, cut bandwidth costs dramatically, and make pages load in milliseconds instead of seconds by eliminating redundant network round trips. Improperly configured caching, on the other hand, can serve stale content to users, break application functionality, and create confusing debugging scenarios where changes are not visible. The caching system is governed by two primary headers — Cache-Control and ETag/Last-Modified — along with supporting headers like Vary, Expires, and Age.
The Cache-Control header is the most important and versatile caching header, supporting a rich set of directives that control both who can cache a response and for how long. The max-age directive specifies the maximum time in seconds that a response is considered fresh — for example, Cache-Control: max-age=86400 means the response can be served from cache for 24 hours without revalidation. The s-maxage directive overrides max-age for shared caches (like CDNs) while allowing browsers to use their own max-age. The no-cache directive forces revalidation before use (despite its name, it does not prevent caching — it requires the cache to check with the server before serving the stored copy). The no-store directive truly prevents caching — the response must not be stored anywhere. The private directive restricts caching to the browser only (disallowing CDN caching), which is essential for personalized or sensitive content. The public directive explicitly allows shared caching, even if the response would normally be uncacheable (e.g., responses with Authorization headers).
Conditional request headers work in tandem with Cache-Control to provide efficient revalidation. When a cache entry expires, the browser sends a conditional request using If-None-Match (with the previously received ETag value) or If-Modified-Since (with the previously received Last-Modified date). If the resource has not changed, the server responds with 304 Not Modified — saving the bandwidth of re-transmitting the full resource body while confirming the cached copy is still valid. ETags are preferred over Last-Modified because they provide more granular validation (ETags change whenever the content changes, whereas Last-Modified has only 1-second resolution). The Vary header tells caches which request headers affect the response variant — for example, Vary: Accept-Encoding means the server may return different content for different compression methods, and the cache must store separate entries for each variant. Incorrectly omitting Vary can cause the cache to serve a compressed response to a client that cannot decompress it, or vice versa.
| Cache-Control Directive | Effect | Use Case | Example |
|---|---|---|---|
| max-age=<seconds> | Cache is fresh for N seconds | Static assets with hash filenames | max-age=31536000 |
| s-maxage=<seconds> | Overrides max-age for shared caches | CDN-specific TTLs | s-maxage=86400, max-age=300 |
| no-cache | Revalidate before using cached copy | HTML documents that change frequently | no-cache |
| no-store | Never cache the response | Sensitive data, banking pages | no-store |
| private | Only browser can cache (no CDN) | Personalized user content | private, max-age=300 |
| public | Allow shared caches | Public resources behind auth | public, max-age=3600 |
| must-revalidate | Must revalidate once stale | Prevents serving stale content | max-age=3600, must-revalidate |
| immutable | Content never changes while fresh | Versioned static assets | max-age=31536000, immutable |
| stale-while-revalidate | Serve stale while refreshing in background | Non-critical API data | max-age=60, stale-while-revalidate=300 |
A practical caching strategy for modern web applications follows a simple pattern: cache immutable static assets (JavaScript, CSS, images, fonts with content-hash filenames) aggressively with Cache-Control: public, max-age=31536000, immutable, cache HTML documents conservatively with Cache-Control: no-cache (forcing revalidation on every visit), and cache API responses according to their volatility. The immutable directive is particularly powerful — it tells the browser that the resource will never change during its freshness lifetime, eliminating the revalidation request that browsers typically send when a user refreshes the page. For content-hash filenames (like app.a3f7b2c4.js), this is safe because any change to the content produces a new filename, making the old cache entry irrelevant.
How to Inspect HTTP Headers
Inspecting HTTP headers is an essential skill for debugging web applications, verifying security configurations, and understanding how servers and clients communicate. Every modern browser includes Developer Tools that provide detailed visibility into HTTP traffic, but the level of detail and presentation varies across tools. The most common method is using Chrome DevTools or Firefox Developer Tools: open the Network panel (F12 > Network), reload the page, and click on any request to see both the request and response headers in the Headers tab. Chrome DevTools displays headers grouped by category (General, Request Headers, Response Headers), and you can toggle between parsed and raw views. Firefox provides similar functionality with a slightly different layout, and includes a "Headers" tab that shows both sent and received headers with color-coded security indicators.
Press F12 (or right-click → Inspect) and navigate to the Network tab. Reload the page to capture fresh requests — you will see a list of every HTTP request made by the page.
Click on any request in the Network panel to view its details. The Headers tab shows both request headers (sent by the browser) and response headers (sent by the server) in organized sections.
Look for critical security headers in the response: Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, and X-Frame-Options. If any are missing, your site may be vulnerable to XSS, clickjacking, or MITM attacks.
Run curl -I https://example.com in your terminal to see response headers instantly, or use curl -v for the full request and response including TLS handshake details.
Paste your URL into securityheaders.com or observatory.mozilla.org for a comprehensive security header audit with grades and specific recommendations for improvement.
For more advanced inspection, command-line tools offer greater flexibility and scripting capability. curl is the most widely used tool for HTTP debugging — curl -v https://example.com shows the full request and response headers along with the TLS handshake details. The -I flag sends a HEAD request to see only the response headers without the body, while -H allows you to add custom request headers for testing. httpie (the http command) provides a more user-friendly alternative with syntax highlighting and JSON formatting by default. For analyzing security headers specifically, tools like securityheaders.com (Scott Helme's online scanner) and observatory.mozilla.org (Mozilla Observatory) evaluate your headers against best practices and provide a grade with specific recommendations. These tools are invaluable for verifying that your security headers are correctly configured and that no headers are missing.
For programmatic inspection, you can use server-side logging, network monitoring tools, and browser extensions. On the server side, most web frameworks provide middleware for logging request headers — Express.js has morgan, Django has django-extensions, and Go's net/http package allows direct header inspection via r.Header. Network monitoring tools like Wireshark capture raw TCP traffic including HTTP headers, which is useful for debugging at the network layer. Browser extensions like HTTP Header Live (Firefox) and ModHeader (Chrome) allow real-time header inspection and modification, which is particularly useful for testing CORS configurations and authentication flows. Whichever method you choose, make header inspection a regular part of your development workflow — catching misconfigured headers early prevents security vulnerabilities and performance problems in production.
Inspect HTTP Headers
Check any website's headers for security, caching, and CORS configuration in seconds.
Try HTTP Header Checker
Inspect HTTP request and response headers for any URL — check security, caching, and CORS configuration.
Frequently Asked Questions
Related Articles

RFC 4648 Base64 Encoding Explained — How It Works, Examples & Free Tool
Master RFC 4648 Base64 encoding with step-by-step examples. Understand the algorithm, 33% overhead, URL-safe variant, and try our free online Base64 encoder/decoder tool.

JWT Tokens Explained: How Authentication Works in Modern Web Apps — and How to Decode Them
Learn everything about JWT tokens — from their three-part structure to common vulnerabilities, best practices, and how to decode them.

Regex Survival Guide: 15 Patterns Every Developer Should Know — and How to Test Them
Master the 5 regex concepts that compose 90% of patterns, then apply them to 15 battle-tested patterns you will use constantly.