Headers are defence in depth. None fixes a vulnerability; they limit what an attacker can do with one.
The quick ones
HSTS tells the browser to use HTTPS for this domain from now on, closing the gap where a user types the address and the first request goes over HTTP. Only set it once HTTPS works everywhere, it's sticky, and a broken certificate becomes an outage rather than a warning. Start with a short max-age and raise it.
nosniff stops the browser guessing content types. Without it, a file you serve as text can be interpreted as JavaScript.
Referrer-Policy controls how much of your URL is sent to other sites. The default leaks full paths, which matters if your URLs contain identifiers or tokens.
Permissions-Policy disables browser features you don't use. Low value on its own, no cost.
X-Frame-Options and clickjacking
X-Frame-Options: DENY prevents your site being framed. It's superseded by CSP's frame-ancestors, but harmless to set both while older browsers exist.
Content-Security-Policy
The one that does real work, and the one that takes effort.
CSP restricts where scripts, styles, images and connections can come from. A well-configured policy means an injected script tag doesn't execute, because the source isn't allowed.
Content-Security-Policy: default-src 'self'; script-src 'self'; frame-ancestors 'none'
That's the ideal and it will break most existing sites immediately. Inline scripts, inline styles, third-party analytics, embedded widgets all stop working.
The workable approach is incremental:
Deploy
Content-Security-Policy-Report-Onlywith a report endpoint. Nothing breaks; you collect violationsRead what's actually loading. It'll be more than you expected
Tighten until the reports are only things you want to block
Switch to enforcing
Avoid unsafe-inline in script-src, it removes most of the value. Use nonces or hashes for inline scripts you genuinely need.
Frameworks that inject inline styles make style-src harder. unsafe-inline for styles is a weaker compromise than for scripts, and often the pragmatic landing point.
Cookies
Not headers exactly, but the same layer:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
HttpOnly keeps JavaScript from reading the cookie, so an XSS doesn't immediately become a stolen session. SameSite=Lax handles most CSRF.
Where to set them
Once, centrally. Reverse proxy, CDN, or framework middleware. helmet for Express, SECURE_* settings for Django, next.config.js headers for Next.js.
Not per-route. Anything applied per-route gets missed on the route added next month.
Checking
Load your site and read the response headers in the network tab. Several free scanners will grade the configuration for you, and a header check belongs in CI so a proxy change doesn't quietly remove them.