Guide
HTTP security headers explained
A handful of response headers change what a browser will allow on your page. Each one closes a specific attack, and none of them is a substitute for the others.
Security headers are instructions a server sends with every response, telling the browser to enforce restrictions it would not apply by default. They are unusual among security controls in that they cost almost nothing to deploy and are enforced by the client — which is also their limitation, since they protect only visitors whose browsers honour them.
None of them fixes a vulnerability. What they do is reduce the damage when one exists.
Strict-Transport-Security
HSTS tells the browser to use HTTPS for this host for a period, regardless of what a link or a typed URL says.
Strict-Transport-Security: max-age=31536000; includeSubDomains
The attack it closes is the first request. A visitor typing example.com makes a plaintext
request that an attacker on the network can intercept before any redirect happens. HSTS removes that
window for everyone who has visited before.
The care it needs: includeSubDomains applies to every subdomain, including any that cannot serve HTTPS,
and max-age cannot be withdrawn from browsers that already cached it. Start with a short
max-age, confirm nothing breaks, then raise it.
Content-Security-Policy
CSP restricts where a page may load resources from and whether inline code may run. It is the most powerful header here and the only one that needs real work.
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'
Its value is that it turns cross-site scripting from "attacker runs arbitrary code" into
"attacker injects markup the browser refuses to execute". Without 'unsafe-inline',
an injected <script> tag simply does not run.
The directives that carry most of the weight:
default-src 'self'— the fallback for everything not stated separately.-
object-src 'none'— blocks Flash-era plugin embeds, a legacy injection route with no modern use. -
base-uri 'self'— stops an injected<base>tag repointing every relative URL on the page. -
frame-ancestors— who may frame you. Supersedes X-Frame-Options, and is ignored in a<meta>tag, so it must be a real header.
The honest difficulty: a strict policy conflicts with inline styles and scripts,
and third-party embeds frequently need origins added one at a time. Adding
'unsafe-inline' to make the console quiet removes most of the protection you were buying.
X-Content-Type-Options
X-Content-Type-Options: nosniff
Browsers historically guessed a resource's type when the declared Content-Type
looked wrong. That guessing could turn an uploaded text file into executable script.
nosniff disables it.
One value, no configuration, no realistic way to break a correctly-served site. If you set only one header, set this one.
X-Frame-Options and frame-ancestors
X-Frame-Options: DENY Controls whether other sites may load yours in a frame, which is the mechanism behind clickjacking — an invisible frame of your page positioned under a decoy button so a visitor's click lands somewhere they did not intend.
frame-ancestors in CSP does the same thing with more precision. Sending both is normal
and sensible: the CSP directive for modern browsers, the older header for anything that does not implement
it.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin Controls how much of the current URL is sent when a visitor follows a link away. The default leaks full URLs to other sites, which matters whenever a path contains a reset token, a document identifier, or anything else you would not put in a public link.
strict-origin-when-cross-origin is the practical choice: full URL within your own site,
origin only to others, nothing at all when downgrading to HTTP.
Permissions-Policy
Permissions-Policy: camera=(), microphone=(), geolocation=() Disables browser features for the page and anything it embeds. An empty list means "no one, including me". Its main value is over third-party frames: an embed cannot request a camera your page has already disabled.
The cross-origin isolation headers
Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and
Cross-Origin-Resource-Policy address side-channel attacks of the Spectre family by separating
browsing contexts.
They are worth the effort if you use SharedArrayBuffer or high-resolution timers, which
require them. For an ordinary site, COOP: same-origin is a reasonable addition and the
other two are usually more disruption than they are worth.
A sensible order to adopt them
- X-Content-Type-Options — one line, no risk.
- Referrer-Policy — one line, prevents URL leakage immediately.
- Strict-Transport-Security — short max-age first, then raise it.
- X-Frame-Options / frame-ancestors — trivial unless you are framed on purpose.
- Content-Security-Policy — start in report-only mode and iterate.
To see which of these a site currently returns — along with its redirect chain and the rest of its response headers — run it through the HTTP headers checker. Since HSTS is only meaningful alongside a valid certificate, the SSL certificate checker is the natural next step.
Frequently asked questions
Is a missing security header a vulnerability?
Not by itself. These headers reduce the impact of a flaw elsewhere — they do not create one when absent. A site with no CSP is not compromised; it simply has no second line of defence if a cross-site scripting bug is ever introduced. Treat a missing header as a hardening opportunity, not a finding.
Which headers should a simple site actually set?
Strict-Transport-Security, X-Content-Type-Options: nosniff, and a Referrer-Policy. All three are one line each, carry essentially no risk of breaking a working site, and are useful immediately. Content-Security-Policy is the highest-value header but needs real effort to get right.
Does X-XSS-Protection still matter?
No. The browser filter it controlled had its own vulnerabilities and has been removed from every major browser. Modern guidance is to omit it entirely, or set it to 0. Content-Security-Policy replaced it.
Why does my CSP report violations from browser extensions?
Extensions inject scripts and styles into pages, and those injections are subject to your policy. Violations naming extension URLs are not caused by your site and cannot be fixed from it. Filter them out before drawing conclusions from a report endpoint.
Try it yourself
Everything above is easier to follow against a real answer.