HTTP Response Headers Explained: Security, Caching, and CORS

The small text lines your server sends before the page body control browser security, caching, and cross-origin access. Here's what each one does and how to get them right.

Updated July 5, 2026 · ~8 min read

Every HTTP response starts with a status line and a stack of headers before a single byte of your page arrives. Those headers decide whether the browser upgrades to HTTPS, blocks an injected script, serves a cached copy, or refuses to hand your JavaScript a cross-origin response. Getting them right is one of the highest-leverage things you can do for a site's security and speed.

What HTTP headers are

An HTTP message is split into a start line, a block of headers, a blank line, and an optional body. Headers are simple Name: value pairs of metadata that travel alongside the payload. They come in two directions:

This guide focuses on response headers, because those are the ones you configure on your server or CDN and the ones that make or break security and caching behaviour.

Security headers that matter

Modern browsers enforce a set of opt-in protections that are only active if the server explicitly asks for them. Missing them is the most common finding in any web security scan. Here are the ones worth adding to nearly every site.

Strict-Transport-Security (HSTS)

Tells the browser to only ever connect over HTTPS for the given duration, defeating protocol-downgrade and SSL-stripping attacks. Once seen, the browser refuses plain http:// to your domain even if a user types it. A safe value is Strict-Transport-Security: max-age=31536000; includeSubDomains. Only add preload once you are certain every subdomain supports HTTPS, because it is hard to undo.

Content-Security-Policy (CSP)

The most powerful and most complex header. It whitelists exactly which sources scripts, styles, images, and frames may load from, which is your strongest defence against cross-site scripting (XSS). A conservative starting point is Content-Security-Policy: default-src 'self', then loosen it per resource type as needed. Prefer frame-ancestors 'none' here over the older X-Frame-Options when you can.

X-Content-Type-Options

The single value nosniff stops browsers from second-guessing the declared Content-Type and executing, say, an uploaded image as JavaScript. It is a one-line, no-downside win: X-Content-Type-Options: nosniff.

X-Frame-Options / frame-ancestors

Prevents clickjacking by controlling whether your pages can be embedded in an <iframe>. Use X-Frame-Options: DENY (or SAMEORIGIN if you legitimately frame your own pages). The modern equivalent is the CSP frame-ancestors directive, which browsers honour over the older header.

Referrer-Policy & Permissions-Policy

Referrer-Policy: strict-origin-when-cross-origin limits how much of your URLs leak to third parties in the Referer header — you send the full path to your own origin but only the bare origin off-site. Permissions-Policy switches off browser features you don't use, e.g. Permissions-Policy: geolocation=(), camera=(), microphone=(), shrinking the attack surface.

Security header reference

HeaderPurposeSafe example value
Strict-Transport-SecurityForce HTTPS, block downgrade attacksmax-age=31536000; includeSubDomains
Content-Security-PolicyWhitelist resource sources, stop XSSdefault-src 'self'
X-Content-Type-OptionsDisable MIME-type sniffingnosniff
X-Frame-OptionsPrevent clickjacking via framingDENY
Referrer-PolicyLimit referrer data sent off-sitestrict-origin-when-cross-origin
Permissions-PolicyDisable unused browser featuresgeolocation=(), camera=()
Watch out: a Content-Security-Policy that is too strict will silently break your own scripts and styles — test with Content-Security-Policy-Report-Only first so violations are logged, not enforced, until you're confident the policy is clean.

Caching headers: Cache-Control, ETag & Last-Modified

Caching headers decide whether the browser and any CDN in between keep a copy of your response and for how long. Done well, they eliminate redundant downloads; done badly, they either serve stale pages or make every visit re-fetch everything.

Cache-Control

This is the primary caching header, and it takes a list of directives:

A typical setup: HTML pages get Cache-Control: no-cache so they always revalidate, while hashed static assets get Cache-Control: public, max-age=31536000, immutable.

ETag and Last-Modified (revalidation)

These two headers make revalidation cheap. An ETag is a fingerprint of the response body; Last-Modified is a timestamp. When a cached copy expires, the browser sends a conditional request with If-None-Match (the ETag) or If-Modified-Since (the date). If nothing changed, the server replies 304 Not Modified with an empty body — the browser reuses its cached copy and you save the full download. This is exactly what no-cache relies on to stay fast.

CORS headers and why requests get blocked

By default, browsers enforce the same-origin policy: JavaScript on https://app.example.com cannot read a response from https://api.other.com. Cross-Origin Resource Sharing (CORS) is the mechanism a server uses to opt in and grant that access via response headers.

The classic "CORS is blocking me" confusion: the request usually reaches the server and succeeds there. The browser simply refuses to expose the response to your script because the required Access-Control-Allow-Origin header was missing or didn't match your page's origin. That means the fix lives on the server, not in your fetch call. For anything beyond a simple GET, the browser first sends a preflight OPTIONS request — if that preflight doesn't return the right allow headers, the real request never fires.

Security gotcha: you cannot combine a wildcard Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. Browsers reject that pairing because it would let any site make authenticated requests on a user's behalf. When credentials are involved, you must echo back a specific, validated origin instead of *.

Content headers: type & encoding

Two response headers describe the body itself:

Inspecting and debugging headers

You cannot fix headers you cannot see. There are three practical ways to inspect them:

  1. Browser DevTools. Open the Network tab, reload, click the request, and read the Response Headers panel. Best for the site you're currently on.
  2. Command line. curl -I https://example.com prints just the response headers; add -H 'Accept-Encoding: gzip' to see compression negotiation in action.
  3. An online header checker. It fetches the URL server-side and lists every header at once — handy for a site you're not logged into or to compare responses without your local browser cache in the way.

Common misconfigurations to look for while debugging:

Inspect any site's HTTP headers

See every response header, spot missing security headers, and debug caching or CORS in seconds — free, no signup.

Open the Header Checker →

Frequently asked questions

What are the most important security headers?
The core set is Strict-Transport-Security (HSTS) to force HTTPS, Content-Security-Policy to control which scripts and resources load, X-Content-Type-Options: nosniff to stop MIME sniffing, X-Frame-Options or a CSP frame-ancestors directive to prevent clickjacking, and Referrer-Policy to limit what URL data leaks to third parties. Adding these four or five headers closes the most common browser-side attack vectors.
What does Cache-Control do?
Cache-Control tells browsers and CDNs whether a response may be stored and for how long. Directives like max-age=3600 set the freshness lifetime in seconds, public and private say who may cache it, no-cache forces revalidation before reuse, and no-store forbids caching entirely. It is the primary header that governs how long visitors keep serving a cached copy of your page or asset.
Why am I getting a CORS error?
A CORS error means the browser blocked a cross-origin request because the server did not return an Access-Control-Allow-Origin header that matches the page's origin. The request usually reaches the server and even succeeds there, but the browser hides the response from your JavaScript because the permission header is missing or wrong. The fix is on the server: return the correct Access-Control-Allow-Origin value, not a change in your client code.
What is the difference between no-cache and no-store?
no-cache does allow storing the response, but the cache must revalidate with the server (using ETag or Last-Modified) before serving it again, so you get a fast 304 when nothing changed. no-store forbids keeping any copy at all, so every request is a full fresh download. Use no-cache for content that changes but can be revalidated cheaply, and no-store only for sensitive data that must never be written to disk.
How do I see a site's response headers?
Open your browser's DevTools, go to the Network tab, reload the page, click the document request, and read the Response Headers section. From a terminal you can run curl -I https://example.com to print just the headers. An online header checker fetches the URL server-side and lists every response header at once, which is handy for testing a site you are not currently on.

Related tools & guides