When a browser loads a page, the server sends headers before the HTML. A handful of them turn on protections that are built into every modern browser but are off, or loose, by default. They cost almost nothing to add and block whole classes of attack.
How to see a site's headers
curl -sI https://example.com-I asks for headers only; -s hides the progress bar. Paste the output into the security headers analyzer to check it. The analyzer only reads what you paste; it does not contact any site.
The headers that matter
Strict-Transport-Security (HSTS)
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadTells the browser: for the next max-age seconds (here two years), only ever connect to this site over HTTPS, even if the user types http://. This defeats SSL-stripping attacks on hostile networks.
- Only send it over HTTPS, and only once HTTPS works everywhere on the domain.
includeSubDomainscovers every subdomain, so be sure they all support HTTPS.preloadis a request to be added to browsers' built-in HSTS list. Removal takes time, so add it deliberately.
Content-Security-Policy (CSP)
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'CSP is an allowlist of where the page may load scripts, styles, images, frames and connections from. If an attacker injects <script src="https://evil.example/x.js">, a CSP without that origin blocks it.
- The strongest policies use nonces or hashes for scripts instead of allowing
'unsafe-inline'. - Roll out with
Content-Security-Policy-Report-Onlyfirst, fix what breaks, then enforce. - A policy containing
'unsafe-inline'for scripts still helps (it limits external sources, framing, plugins and more) but gives much weaker protection against injected inline scripts.
X-Content-Type-Options
X-Content-Type-Options: nosniffStops browsers from guessing a file's type. Without it, a file uploaded as .txt could be sniffed and executed as script or rendered as HTML.
Frame protection (clickjacking)
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENYClickjacking loads your site invisibly inside an attacker's page and tricks users into clicking. frame-ancestors in CSP is the modern control; X-Frame-Options is the older header still sent for compatibility. Use 'self' or SAMEORIGIN if your own site needs to frame its pages.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-originControls how much of the current URL is sent to other sites when a user clicks a link. Full URLs can contain search terms, tokens or IDs. strict-origin-when-cross-origin sends only the origin to other sites and nothing when going from HTTPS to HTTP. Modern browsers use it as the default, but sending it explicitly documents your intent.
Permissions-Policy
Permissions-Policy: camera=(), microphone=(), geolocation=()Turns off powerful browser features your site does not use, so injected code or embedded frames cannot request them.
Headers you can drop
- X-XSS-Protection: controlled an XSS filter that browsers have removed. Omit it or send
0. - Server and X-Powered-By: not security controls; they advertise your software and version. Removing them does not fix vulnerabilities but gives attackers less free information.
A sensible starting set
| Header | Starting value |
|---|---|
| Strict-Transport-Security | max-age=31536000; includeSubDomains |
| Content-Security-Policy | Start from default-src 'self', then allow what you actually use |
| X-Content-Type-Options | nosniff |
| Referrer-Policy | strict-origin-when-cross-origin |
| Permissions-Policy | Disable features you do not use |
| X-Frame-Options | DENY (plus frame-ancestors in CSP) |
Summary
- A few response headers switch on strong browser protections.
- HSTS forces HTTPS; CSP limits where content loads from; nosniff, frame protection, Referrer-Policy and Permissions-Policy close smaller gaps.
- Headers add defence in depth; they do not replace fixing the underlying bugs.