HTTP security headers explained: the six that matter
What Content-Security-Policy, HSTS, X-Frame-Options and friends actually protect against — and how to deploy them without breaking your site.
HTTP response headers are instructions your server sends the browser alongside every page. A handful of them tell the browser to enforce security rules it would otherwise skip. They are cheap to deploy, they apply to every visitor automatically, and getting them wrong is one of the most common findings on otherwise well-run sites. This is a tour of the six that matter, what each one actually stops, and the mistakes that quietly turn them into decoration.
One framing to keep in mind throughout: security headers are defence-in-depth, not a fix. A Content-Security-Policy reduces the damage a cross-site scripting bug can do — it does not remove the bug. Treat these headers as the seatbelt, not the reason to drive carefully.
Content-Security-Policy (CSP)
Mitigates: the impact of cross-site scripting (XSS) and content injection. A CSP tells the
browser which sources it is allowed to load scripts, styles, images and frames from. Even if an attacker
manages to inject markup into your page, a strict policy means the browser refuses to execute an inline
<script> or fetch code from an attacker-controlled host.
Safe example:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'self'; base-uri 'self'
The gotcha: adding 'unsafe-inline' or 'unsafe-eval' to
script-src defeats most of the point. 'unsafe-inline' re-permits exactly the
inline-script execution that XSS relies on. Wildcards like script-src * or a bare
https: scheme source are almost as bad — they let scripts load from anywhere. If your site
needs inline scripts, move to a nonce- or hash-based policy rather than opening the door for everything.
CSP is also easy to ship in a broken-but-quiet state, so roll it out with
Content-Security-Policy-Report-Only first, watch what it would have blocked, then enforce.
What it does not do: CSP does not sanitise your inputs or patch the injection flaw. It limits the blast radius. A site can have a flawless CSP and still be vulnerable — the policy just makes the vulnerability far less useful to an attacker.
Strict-Transport-Security (HSTS)
Mitigates: protocol downgrade and SSL-stripping man-in-the-middle attacks. HSTS tells the
browser to only ever contact your domain over HTTPS, for a set duration, and to refuse to connect if the
certificate is invalid. Without it, a user who types yourdomain.com makes a first request over
plain HTTP that an attacker on the network can intercept before the redirect to HTTPS ever happens.
Safe example:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
The gotcha: includeSubDomains and preload are effectively
one-way doors. includeSubDomains forces every subdomain to be HTTPS-only — if you have
an internal or legacy host that still serves plain HTTP, it will break. preload bakes your
domain into a list shipped inside browsers themselves; removal is slow and out of your hands. Start with a
short max-age (say a few minutes) to confirm nothing breaks, then raise it. Only add
includeSubDomains and preload once you are certain every subdomain is ready.
What it does not do: HSTS does not encrypt anything itself — TLS does that. It also cannot protect the very first visit to a domain the browser has never seen (the trust-on-first-use gap), which is exactly what the preload list closes. And it is meaningless if your certificate or protocol configuration is weak in the first place; see deprecated TLS versions for the layer underneath it.
X-Frame-Options
Mitigates: clickjacking. This header controls whether your page can be embedded inside a
<frame>, <iframe> or <object> on another site. In a
clickjacking attack, your real page is loaded invisibly over a decoy so that a victim's click lands on your
"delete account" or "transfer" button without them realising.
Safe example:
X-Frame-Options: DENY
Use SAMEORIGIN instead if your own application legitimately frames its own pages.
The gotcha: X-Frame-Options is the older mechanism and cannot express "allow
these two specific partners to frame me." The modern replacement is the CSP directive
frame-ancestors, which browsers honour in preference to this header. Set both for coverage —
frame-ancestors 'self' in your CSP plus X-Frame-Options: SAMEORIGIN — but make
sure they agree, or you will confuse yourself later.
What it does not do: nothing beyond framing. It is not a general anti-embedding or anti-scraping control.
X-Content-Type-Options
Mitigates: MIME sniffing. Some browsers historically tried to guess a response's content type when it looked wrong, which let an attacker smuggle executable script through a file the server labelled as something harmless (an uploaded "image", for instance). This header switches that guessing off.
Safe example:
X-Content-Type-Options: nosniff
The gotcha: nosniff only helps if your server actually sends correct
Content-Type headers. If you serve a stylesheet as text/plain, a strict browser
will now refuse to load it. The header is the easy part; getting content types right across your responses
is the work.
What it does not do: it does not validate uploads or scan file contents. It removes one specific browser behaviour that turned mislabelled files into a code-execution path.
Referrer-Policy
Mitigates: referrer leakage. The Referer header tells the next site which page
a request came from. Modern browsers already default to strict-origin-when-cross-origin, so they
send only your bare origin — not the full path or query — to other sites. The exposure is when that default
is undone: an older client, or a server or framework configured with a weaker legacy policy, that sends the
full URL. If your URLs carry session tokens, reset tokens, or internal identifiers in the query string, those
then leak to third-party analytics, ad networks, or any external resource your page loads.
Safe example:
Referrer-Policy: strict-origin-when-cross-origin
This is also the current browser default — setting it explicitly makes that behaviour intentional and consistent across clients and frameworks rather than left to chance. It sends the full path only within your own site, the bare origin (scheme and host, no path or query) to other sites, and nothing at all when downgrading from HTTPS to HTTP.
The gotcha: the danger is loosening it, not leaving it. The old default
no-referrer-when-downgrade still exposes full URLs to any HTTPS third party, and
unsafe-url sends them everywhere — both undo the protection. The tightest option,
no-referrer, can break legitimate flows that rely on the referrer (some payment gateways and
analytics). Whatever policy you choose, the deeper fix is to never put secrets in URLs in the first place.
Permissions-Policy
Mitigates: abuse of powerful browser features. This header lets you explicitly disable APIs your site does not use — camera, microphone, geolocation, USB, payment — so that neither your own page nor any embedded third-party content can silently invoke them. It shrinks the attack surface available to injected or compromised scripts.
Safe example: deny everything you do not need.
Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=()
An empty allow-list () means "no origin may use this feature." Grant a feature to your own
origin with geolocation=(self) only where you genuinely need it.
The gotcha: this header replaced the older Feature-Policy, and the syntax is
different — copying an old Feature-Policy string verbatim will not work. Adopt a deny-by-default
posture and open up features one at a time, rather than listing a few to block and leaving the rest open.
What it does not do: it governs browser feature access, not data handling. It will not stop a script you have deliberately loaded from doing whatever those granted features allow.
A quick reference
| Header | Primary attack it mitigates |
|---|---|
Content-Security-Policy | XSS / content injection impact |
Strict-Transport-Security | Protocol downgrade / SSL stripping |
X-Frame-Options | Clickjacking |
X-Content-Type-Options | MIME sniffing |
Referrer-Policy | Referrer / token leakage |
Permissions-Policy | Abuse of browser feature APIs |
Two things worth stressing before you deploy. First, headers interact: your CSP's
frame-ancestors and X-Frame-Options should agree, and an over-broad CORS policy
can undermine the isolation these headers assume — see
CORS misconfiguration. Second, none of
these is a substitute for fixing the underlying bug. They are the layer that limits how much a mistake
elsewhere can cost you.
How Cyber Report checks this
An external Cyber Report scan reads your live response headers without touching your server's
internals: it flags missing Strict-Transport-Security, Content-Security-Policy,
X-Frame-Options, X-Content-Type-Options and Referrer-Policy, grades
the quality of any CSP you do have — catching 'unsafe-inline', 'unsafe-eval',
wildcard and plain-http: sources, and a missing object-src or
frame-ancestors — and checks whether your HSTS configuration is actually preload-list eligible.
It also notes server version details leaked in Server and X-Powered-By headers.
See what we scan, or run a scan on your own domain to see where you stand.