The Seven Headers Behind the Grade, and Why They Matter
Each header answers one question the browser would otherwise answer badly. This page takes them in the order the checker weights them, with the value to send, what it breaks, and how to roll it out.
Content-Security-Policy
Every other header on this page prevents something. CSP limits the damage after the thing you did not prevent: a script injected through a comment field, a compromised third-party tag, a stored XSS in the CMS. The policy is a list of directives, each naming the sources a resource type may come from. script-src 'self' https://www.googletagmanager.com means scripts load from your own origin and Google's tag manager, and from nowhere else, including nowhere inline. A browser that meets an injected <script src="https://evil.example/x.js"> refuses it and reports a violation.
The catch is inline scripts. Most sites have them (analytics snippets, theme code, a widget), and the easy way to keep them working is 'unsafe-inline', which allows every inline script including the injected one. That is why the checker grades a policy with 'unsafe-inline' in script-src as weak: it still blocks remote hosts, but the common attack goes straight through. The fix is nonces: the server puts a random value in the header ('nonce-abc123') and the same value on each legitimate script tag; anything without it is blocked. With 'strict-dynamic', scripts loaded by a nonced script are trusted too, which is what makes tag managers work under a strict policy.
# host-list policy (static sites, no server code)
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' https://*.google-analytics.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'
# nonce policy (server sets a fresh nonce per response)
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
Roll it out as Content-Security-Policy-Report-Only first. Nothing is blocked, violations appear in the browser console (and at a report endpoint if you set one), and you add what the site actually needs. Switch to the enforcing header when the console is quiet on every page type. The four directives that every policy should carry regardless: object-src 'none', base-uri 'self', frame-ancestors, form-action 'self'.
Strict-Transport-Security
A visitor who types your domain into the address bar gets http:// first, and the redirect to HTTPS happens over an unencrypted request that a network attacker can intercept. HSTS tells the browser: for the next max-age seconds, never send this host a plain HTTP request; rewrite it to HTTPS before it leaves. includeSubDomains extends that to every subdomain, and preload signals that you want the host baked into browsers' built-in list, so even the first visit is protected.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Two cautions. Every page and every subdomain must already work over HTTPS, including the staging server on a subdomain and the old marketing site nobody maintains; HSTS locks visitors out of anything that does not. And once sent, it lives in the visitor's browser until max-age expires, so test with a short max-age (the generator offers five minutes) before committing to a year. Preload is a further step: removal from the list takes months and a browser release cycle.
X-Content-Type-Options
Browsers used to look at the bytes of a response and, if they looked like a script, run them as one, whatever Content-Type said. That let an uploaded "image" or a text file become executable. nosniff switches the guessing off. It has one value, no options and no downside; it is the header everyone should have had a decade ago.
X-Content-Type-Options: nosniff
Framing: frame-ancestors and X-Frame-Options
Clickjacking puts your page in an invisible frame over a fake page, so a visitor who thinks they are clicking "play" is clicking "delete account" on your site, logged in. The defence is telling browsers who may frame you. The modern form is the CSP directive frame-ancestors 'self' (or 'none', or a list of hosts); the older X-Frame-Options: SAMEORIGIN or DENY works everywhere and is worth keeping alongside. ALLOW-FROM is dead: current browsers ignore it.
Referrer-Policy
When a visitor follows a link, the browser tells the destination where they came from. With the wrong policy that is the full URL, including paths like /account/reset?token=... or a search query. strict-origin-when-cross-origin sends only the origin to other sites and nothing at all over a downgrade to HTTP; it is the browser default now, and stating it protects visitors on browsers that have not caught up. no-referrer sends nothing, which is fine unless your analytics needs to know which of your own pages sent the visitor.
Permissions-Policy
Modern browser features (camera, microphone, geolocation, payment, sensors) can be requested by any script on the page, including third-party ones and scripts inside frames. Permissions-Policy lists which features the page allows and for whom. camera=(), microphone=(), geolocation=() switches those off entirely, so a compromised ad script cannot prompt for the webcam. Two entries with no security purpose but worth including: interest-cohort=() and browsing-topics=() opt the site out of Chrome's advertising-profile APIs.
The Cross-Origin Isolation Headers
Three newer headers, each worth a little. Cross-Origin-Opener-Policy: same-origin severs the reference a page keeps to a window it opened, which blocks some cross-window attacks and is required, with COEP, for SharedArrayBuffer. It breaks OAuth popups that talk back through window.opener; same-origin-allow-popups keeps those working. Cross-Origin-Resource-Policy: same-origin stops other sites embedding your images and scripts. Cross-Origin-Embedder-Policy: require-corp demands that everything you embed opts in, which most third-party content does not; only set it when you need the isolation.
Headers That Cost Points
Server with a version number and X-Powered-By tell an attacker which known vulnerabilities to try; strip the version or remove the header. X-XSS-Protection turned on a browser filter that was removed years ago after it enabled attacks of its own; send 0 or nothing. Public-Key-Pins is obsolete and dangerous. Expect-CT and P3P are dead weight.
Rollout Order
- HTTPS everywhere, with a 301 from http://. Nothing below matters before this.
- nosniff, framing, Referrer-Policy, Permissions-Policy. Safe to add in one deploy.
- HSTS with a short max-age, then a year, then preload if you mean it.
- CSP in report-only mode for a week, then enforced. Nonces when you can change the templates.
- Run the checker after each step. The generator writes all of the above for Apache, nginx, Caddy, Cloudflare, Netlify and Vercel.
Questions People Ask About Security Headers
What are HTTP security headers?
Security headers are response headers a web server sends to tell the browser how to treat the page: always use HTTPS, only run scripts from these hosts, do not let other sites frame this page, do not guess content types. They cost nothing to send and close off whole classes of attack.
Which security headers should every site have?
Six cover most of it: Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options: nosniff, a framing rule (CSP frame-ancestors or X-Frame-Options), Referrer-Policy and Permissions-Policy. HTTPS itself comes first; none of the headers help on plain HTTP.
What does Content-Security-Policy do?
It lists where each kind of resource may load from: scripts, styles, images, fonts, frames, connections. If an attacker injects a script tag pointing at their server, the browser refuses to load it. It is the only header that limits the damage after an injection bug, which is why it is worth the most in the grade.
Is X-Frame-Options still needed if I have CSP frame-ancestors?
Not for current browsers, which use frame-ancestors and ignore X-Frame-Options when both are present. Keeping X-Frame-Options: SAMEORIGIN alongside costs nothing and covers very old clients, so most sites send both.
Can security headers break my site?
Only two of them can. A Content-Security-Policy that omits a host or an inline script blocks that resource, which is why it is rolled out in report-only mode first. Strict-Transport-Security on a site with any HTTP-only page or subdomain locks visitors out of it until max-age expires. The rest have no visible effect on a normal site.
Why does the checker say my CSP is weak when it is present?
Usually because script-src allows unsafe-inline, which permits exactly the inline script injection CSP exists to stop, or because it allows scripts from any host. A policy that blocks nothing an attacker would try earns few points even though the header is there.