gg
Home › Blog › What each security header actually prevents

What each security header actually prevents

2026-08-27 · 4 min read
securitywebhosting

Security headers are instructions attached to a response, telling the browser how to treat the page it has just received. That is a narrow job. They do not inspect incoming requests, they do not authenticate anyone, and nothing about them stands between an attacker and your server. What each one does is close a specific, named class of problem — so the useful way to learn them is one at a time, with the boundary stated.

Framing

Any site can put your page in an iframe. Made transparent and positioned under a decoy, that frame lets a visitor believe they are clicking one thing while actually clicking a control on your page. Two headers refuse it:

X-Frame-Options: DENY and, in a Content-Security-Policy, frame-ancestors 'none'.

The CSP directive is the modern one and it is more expressive — it can name specific origins rather than only allow or deny. The older header is still worth sending alongside it for browsers that do not honour the newer one.

What this does not cover: it says nothing about links to your page, screenshots of it, or anything copying its content. It stops embedding, not attention.

MIME sniffing

When a response arrives with a missing, vague or wrong Content-Type, browsers historically guessed the real type from the bytes. That guess is the problem: a file uploaded and served as something inert can be sniffed into something the browser executes.

X-Content-Type-Options: nosniff turns the guessing off. The declared type is taken at face value, and two cases become hard refusals — a stylesheet not served as text/css is not applied, and a script not served as a JavaScript type does not run.

Worth knowing before you send it: if your host mislabels a file type, nosniff converts a sloppy-but-working load into a visible failure. That is usually what you want, but deploy it with the site in front of you. And it does not validate content — it only stops the browser from second-guessing a label.

Referrer leakage

By default a browser following a link tells the destination which page you came from, in the Referer header, as a full URL. If that URL carries a token, a record identifier, a search term or an email address, it has just been handed to somebody else's server and their logs.

Referrer-Policy: strict-origin-when-cross-origin narrows that: the full URL is sent within your own origin, only the bare origin is sent to another HTTPS site, and nothing at all is sent when the destination is plain HTTP.

What this does not cover: the destination still learns that a visit came from your domain, because the origin is still sent. It is also not a reason to keep putting secrets in URLs — a URL ends up in browser history, in shared links and in your own server logs regardless of what any header says.

Forced HTTPS

Someone typing your domain without a scheme starts with a plain HTTP request. That request is in the clear before any redirect to HTTPS can happen.

Strict-Transport-Security: max-age=31536000; includeSubDomains closes that window from the second visit onward. After one successful HTTPS response carrying the header, the browser remembers the domain for the stated period — here one year in seconds — and upgrades any later plain-HTTP attempt internally, before the request leaves the machine.

Two honest limits. The very first visit from a given browser is not covered, because nothing has been remembered yet; the preload list exists for that case. And this header is a commitment that is awkward to unwind: includeSubDomains means every subdomain must serve working HTTPS, and adding preload means asking to be built into browsers, which is slow to reverse. Send it when HTTPS everywhere is settled, not while you are still moving things.

Two more that are cheap to send

Permissions-Policy: geolocation=(), camera=(), microphone=() uses an empty allowlist, which means nobody — not the page, not any frame inside it. It is a sensible default for a site that never asks for those capabilities, because it removes the possibility of a prompt appearing at all.

Cross-Origin-Opener-Policy: same-origin keeps a cross-origin page that opened yours from holding a usable window reference to it, and Cross-Origin-Resource-Policy: same-origin declines to let other sites pull your resources in as subresources.

What this set is, honestly

For a static site, these headers are defence in depth. Each closes a class of attack that would otherwise be open, they cost nothing to serve, and they are among the few improvements you can make in one file. They are not a firewall and not a substitute for anything: they do not rate-limit, do not patch a vulnerable dependency, do not protect your build pipeline, and do not protect the account that can deploy over the whole site. Treat them as one layer that is worth having, and keep the other layers.

The tool

The Security Headers generator writes a _headers file for Cloudflare Pages or Netlify: a /* block carrying a Content-Security-Policy, HSTS, X-Frame-Options, nosniff, a Referrer-Policy, a Permissions-Policy and the two cross-origin policies above. You choose whether inline styles are permitted, name any extra script or style origins your site loads, and turn HSTS preload on or off. Put the file in your published site root. Then load the site and check the console, because a policy that is too strict for what your pages actually load will fail quietly.

Try it free — with the steps shown

The Security Headers (_headers) runs in your browser and shows exactly how it got the answer, so the method sticks.

Open Security Headers (_headers)

More from the blog