Quick Answer

Security headers are HTTP response headers that tell the browser how to treat your site defensively — force HTTPS on every future visit, block your pages from being framed by another site, stop the browser from guessing file types, control what leaks in the Referer header, and restrict which browser features a page can use. None of them need application logic changes; they need to be set correctly, consistently, on every response, which is where most sites fall short.

The Headers That Don't Get the CSP Spotlight

If you've read our dedicated post on Content Security Policy, you know CSP is the heavyweight of this group — a single header that can block XSS outright by controlling what scripts, styles, and resources a page is allowed to load. This post is deliberately not about CSP. It's about the five other response headers that sit alongside it, each closing a different, narrower gap that CSP doesn't touch.

None of these require a framework, a library, or backend code changes. They're response headers — set them once at your web server, reverse proxy, or CDN, and every response carries them. The catch is that "just a header" is easy to get syntactically wrong in ways that either do nothing or quietly break your own site, so we've verified every example below against the actual specification rather than reciting it from memory.

Strict-Transport-Security: Killing the HTTP Downgrade Window

Even a site that serves everything over HTTPS has a gap: the first time a browser resolves your domain, or any time a user types http:// or clicks an old plain-HTTP link, the very first request can go out unencrypted before a redirect to HTTPS ever happens. An attacker sitting on that network path — a coffee shop Wi-Fi, a compromised router — can intercept that first plaintext request and never let the redirect through. HSTS (HTTP Strict Transport Security) closes that gap by telling the browser, after one successful HTTPS visit, to never attempt HTTP for this domain again, for a set period of time, no matter what the user types or clicks.

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

max-age is in seconds — 63072000 is two years, and browsers commonly recommend at least one year (31536000) before a domain qualifies for preload. includeSubDomains extends the policy to every subdomain of your domain, which is powerful but dangerous if any subdomain still serves plain HTTP; turning this on before every subdomain is HTTPS-ready will break those subdomains outright. preload is a request to be baked directly into the HSTS preload list shipped inside Chrome, Firefox, and Edge themselves, so even a user's very first-ever visit is protected, before the browser has seen your header even once.

Preload is close to permanent in practice: getting removed from the list, once you're baked into browser binaries, takes months to propagate as those browser versions roll out to users. The sane rollout order is to ship a short max-age first, confirm nothing broke, raise it in stages, add includeSubDomains once every subdomain is verified HTTPS-only, and only then submit to hstspreload.org.

X-Frame-Options and frame-ancestors: Stopping Clickjacking

Clickjacking loads your page inside an invisible or disguised iframe on an attacker's site, then tricks a user into clicking something that looks harmless but is actually a button on your page underneath — a "claim your prize" button that's really your bank's "transfer funds" button, positioned pixel-for-pixel under the attacker's fake UI. X-Frame-Options stops this by telling the browser whether your page is allowed to be framed at all.

X-Frame-Options: SAMEORIGIN

Only two values are meaningful today: DENY blocks framing entirely, even by your own site, and SAMEORIGIN allows framing only by pages on your own origin. A third value, ALLOW-FROM, is deprecated and unsupported in modern browsers — if you need to allow specific other origins to frame your page, that job belongs to CSP's frame-ancestors directive instead, which supports a real list of allowed origins:

Content-Security-Policy: frame-ancestors 'self' https://partner.example.com

frame-ancestors is the modern, more capable mechanism and takes precedence in any browser that supports CSP — but it's worth sending X-Frame-Options alongside it anyway, as a one-line, zero-cost fallback for any client that only understands the older header.

X-Content-Type-Options: Stopping MIME Sniffing

Older browsers used to "sniff" the actual bytes of a response to guess its content type, even when the server's Content-Type header said something else. That behavior existed for compatibility with misconfigured servers, but it opens a real hole: if an attacker can get a file with embedded script content hosted on your domain — a user avatar upload, say — a browser that sniffs might decide to execute it as HTML or JavaScript instead of treating it as the harmless image type your server declared.

X-Content-Type-Options: nosniff

There is exactly one valid value, nosniff, and it does exactly one thing: it tells the browser to trust the declared Content-Type and never override it by guessing. There's no configuration decision to make here and no legitimate reason to omit it — it's one line with no downside.

Referrer-Policy: Deciding What Leaks in the Referer Header

Whenever a browser navigates away from your page, or loads a resource from another origin, it can send a Referer header revealing the URL the user came from — including query strings. If a password reset link, a search query, or an internal document ID lives in that URL, it can leak to every third-party script, ad, or embedded resource on the next page. Referrer-Policy controls exactly how much of that URL gets sent.

Referrer-Policy: strict-origin-when-cross-origin

The full set of valid values is no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, and unsafe-url. strict-origin-when-cross-origin is the modern browser default and a sane one for most sites: it sends the full URL to same-origin requests, only the bare origin (no path or query) to cross-origin requests, and nothing at all when downgrading from HTTPS to HTTP. If your URLs ever carry sensitive query parameters, tighten this further to same-origin or no-referrer.

Permissions-Policy: Locking Down Camera, Mic, and Friends

Permissions-Policy (the successor to the older, experimental Feature-Policy header) restricts which browser APIs your page — and any iframe embedded inside it — is allowed to invoke at all: camera, microphone, geolocation, fullscreen, payment, and dozens more. Even if your own code never calls navigator.geolocation, a compromised third-party script or ad tag on your page could try to, and this header shuts that door regardless of what the script attempts.

Permissions-Policy: geolocation=(self "https://maps.example.com"), camera=(), microphone=(), payment=()

The syntax is genuinely different from every other header here: each feature is followed by an allowlist in parentheses. An empty allowlist, (), disables the feature for everyone, including your own top-level page. self permits only your own origin. A quoted origin adds a specific third party — useful if, say, an embedded maps widget genuinely needs geolocation. * allows every origin, which is rarely what you want. A marketing or content site with no legitimate use for any of these APIs can usually disable almost everything by default and allowlist only what it actually uses.

Setting All of Them at Once and Verifying They Landed

A reasonable combined baseline for a typical content site looks like this:

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=()

To check what's actually being sent, run curl -I https://yoursite.com and read the response headers directly — that's ground truth, unaffected by any caching layer's dashboard lying to you. A quick online scanner is useful too, but treat curl as the source of truth since it shows exactly what a real client receives.

Setting these at the CDN or reverse-proxy layer — Cloudflare Transform Rules, an Nginx add_header directive, or a hosting platform's headers() config — is usually more reliable than adding them per-route in application code, because it's applied to every response by default instead of something you have to remember to add every time you create a new route.

Frequently Asked Questions

Do I need HSTS if my whole site is already served only over HTTPS? Yes — a user's very first visit, or a stray plain-HTTP link somewhere, is still vulnerable to a downgrade attack until the browser has cached your HSTS policy from a prior visit; the preload list is what closes the gap even on that first-ever visit.
Is X-Frame-Options obsolete now that CSP has frame-ancestors? Not entirely — frame-ancestors is the more capable, modern mechanism, but X-Frame-Options is worth sending alongside it as a zero-cost fallback for any client or older browser path that only understands the legacy header.
What's the difference between Referrer-Policy and just not putting sensitive data in URLs? Referrer-Policy controls what the browser automatically forwards to other origins during navigation; it doesn't change your own URLs, so sensitive tokens or query parameters still shouldn't live in a URL in the first place regardless of what this header is set to.
Will Permissions-Policy break third-party embeds like a YouTube video or a payment widget? It can, if you disable a feature that embed genuinely needs — blocking fullscreen, for example, breaks a YouTube embed's fullscreen button — so the fix is to explicitly allowlist that origin for the specific feature it needs rather than leaving everything wide open.
Can I set these headers without touching my backend code? Yes — most CDNs and reverse proxies let you set response headers at the edge, and that's usually the more reliable place to do it, since it applies to every response automatically instead of relying on every route in application code remembering to set them.