A handful of HTTP response headers tell the browser to apply protections it would not apply by default. They cost almost nothing to send and block whole classes of attacks — but only if they are actually present on the responses users receive. This is a practical guide to the four that matter most, with sensible starting values.

Strict-Transport-Security (HSTS)

What it prevents: downgrade to plain HTTP. Even with a redirect from http:// to https://, the very first request can be intercepted on a hostile network. HSTS tells the browser to use HTTPS for the domain for a period of time, without trying HTTP at all.

Strict-Transport-Security: max-age=31536000; includeSubDomains
  • Start with a short max-age (for example one day) and increase it once you are sure every subdomain serves HTTPS — includeSubDomains applies to all of them.
  • The header is ignored on plain-HTTP responses; it must be sent over HTTPS.
  • preload asks browsers to ship the rule built in. It is hard to undo, so only add it deliberately.

Content-Security-Policy (CSP)

What it prevents: most cross-site scripting (XSS) damage. A CSP lists where scripts, styles, images and connections may come from; the browser refuses everything else, so an injected <script> or a script from an unexpected domain does not run.

Content-Security-Policy: default-src 'self'; script-src 'self' https://analytics.example.com;
  img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';
  object-src 'none'; base-uri 'self'

CSP is the most powerful header and the easiest to get wrong. A rollout that does not break the site:

  1. Send it as Content-Security-Policy-Report-Only first. The browser reports violations but blocks nothing.
  2. Collect the reports (or watch the browser console) and add the sources the site legitimately needs — analytics, fonts, ads, embeds.
  3. Remove 'unsafe-inline' for scripts where you can, using nonces or hashes for the inline scripts you keep.
  4. Switch to the enforcing header once reports are quiet.

Third-party scripts such as analytics or advertising load further scripts from their own domains. A strict policy has to allow those explicitly, which is a good reason to keep the number of third-party scripts small.

Framing protection: frame-ancestors and X-Frame-Options

What it prevents: clickjacking — loading your page invisibly inside another site's frame and tricking users into clicking buttons on it. The modern control is the CSP directive frame-ancestors; the older header is still useful for old browsers:

Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN

Use 'none' / DENY if the page should never be framed at all.

X-Content-Type-Options: nosniff

What it prevents: MIME sniffing. Without it, a browser may decide that a file served as text or an image "looks like" a script and execute it — dangerous for any site that serves user uploads.

X-Content-Type-Options: nosniff

There is no reason not to send this on every response.

Two more worth a minute

  • Referrer-Policy: strict-origin-when-cross-origin — avoids leaking full URLs (which may contain IDs or tokens) to other sites.
  • Permissions-Policy: camera=(), microphone=(), geolocation=() — turns off powerful browser features the site does not use, including for embedded content.

Where to set them

Setting headers at the web server or proxy (nginx add_header … always;) covers every response, including static files and error pages. Setting them in application middleware gives per-route control. Whichever you choose, watch for gaps: nginx's add_header inside a location block replaces, rather than adds to, headers defined at a higher level.

Check what you actually send

Configuration files say what should be sent; only the real response says what is. Check with curl -sI https://your-site/, the browser's network panel, or an automated scan. Zerethon's Flow includes a security-header check in its website scans: it requests the real pages and reports missing or weak CSP, HSTS, framing and MIME-sniffing headers, with the exact header value (or its absence) as evidence — the same principle as runtime tracing in runtime vs static analysis: measure the real thing.

Limitations

Headers reduce the impact of bugs; they do not fix them. A CSP limits what injected script can do, but the injection is still a vulnerability to fix at its source (output escaping, input validation). Treat headers as a safety net under secure code, not a substitute for it.