← All guides
Guide

The four security headers worth fighting for

Published May 27, 2026. About a six-minute read.

Modern browsers respect a long list of security headers, but only four of them carry weight in our scoring. The other headers (Referrer-Policy, Permissions-Policy, the Cross-Origin family) are useful and we report on them, but they are recommendations, not requirements. The four below are the ones that close real, common attack categories. Skip them at your own risk.

1. Strict-Transport-Security (HSTS)

What it does. Tells the browser to only connect to your site over HTTPS for a specified duration, even if the user types http:// in the address bar or clicks an old plaintext link.

What it prevents. Network-layer attackers who intercept the first request from a new visitor and serve a fake plaintext version of your site. Without HSTS, the browser has to make at least one HTTP request to find out you redirect to HTTPS, and that request is interceptable on a hostile network.

Minimum config:

Strict-Transport-Security: max-age=31536000; includeSubDomains

How to break the site with it. Add includeSubDomains when one of your subdomains is HTTP-only or uses a self-signed certificate. That subdomain will stop loading for any user whose browser has seen the parent header.

We have a separate guide that walks through HSTS, includeSubDomains, and the preload list in detail: How HSTS actually works, and when to use preload.

2. Content-Security-Policy (CSP)

What it does. Enumerates which sources the browser is allowed to load resources from. Scripts, styles, images, fonts, iframes, network calls. Anything not listed gets blocked.

What it prevents. Cross-site scripting (XSS) in most of its forms, third-party script-inclusion attacks (a compromised analytics vendor cannot exfiltrate data from your page if their domain is not in your connect-src), mixed-content sneaks, clickjacking (via frame-ancestors), and a long tail of content-injection bugs.

Minimum config (starter, report-only):

Content-Security-Policy-Report-Only:
  default-src 'self';
  object-src 'none';
  frame-ancestors 'none';
  base-uri 'self';
  report-to csp-endpoint

How to break the site with it. Switch straight to enforcement without report-only. Skip connect-src for a single-page app that makes API calls. Add 'unsafe-inline'“to make it work” and never come back to remove it.

CSP is by far the hardest of the four to get right, and the most valuable when it is. We have a separate guide that walks the whole path: Content-Security-Policy from scratch.

3. X-Frame-Options

What it does. Tells the browser whether your page is allowed to be loaded inside an iframe by other pages.

What it prevents.Clickjacking. The attacker loads your real page in a transparent iframe over their own UI, lines up your “Confirm” button under their “Free iPhone” button, and tricks users into clicking the real one without realizing it.

Config:

X-Frame-Options: DENY

Or, if you legitimately need same-origin iframes:

X-Frame-Options: SAMEORIGIN

How to break the site with it. You cannot, really. If your page does need to be embeddable on partner sites (rare), you should use CSP's frame-ancestors directive instead, which supports an allowlist of domains. X-Frame-Options only supports DENY, SAMEORIGIN, or (in some browsers) ALLOW-FROM with a single origin.

CSP frame-ancestors is the modern equivalent and supersedes X-Frame-Options when both are set. Setting both is fine and adds belt-and-suspenders for older browsers.

4. X-Content-Type-Options

What it does. Tells the browser to trust the Content-Type the server declared and not try to guess.

What it prevents. MIME-sniffing attacks. A file uploaded as a text comment but containing HTML or JavaScript can be interpreted as that type by a browser doing content sniffing, even if your server says it is text/plain. With this header set, the browser refuses to override the declared type.

Config:

X-Content-Type-Options: nosniff

That is the only valid value. The header has no other options, no caveats, no edge cases.

How to break the site with it. You cannot. If your server is sending wrong Content-Type values, you have a bug to fix anyway. This is the closest thing to a free win in the entire list.

Setting them all in one place

nginx:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'" always;

Apache:

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'"

For Cloudflare, all four can be configured under Rules → Transform Rules → Modify Response Headers without touching the origin. For Vercel, Netlify, or other edge platforms, the build-time config supports a headers block that does the same job.

Adding the first three takes minutes and is nearly risk-free. CSP is the long project, and it is the one with the biggest payoff.