Security headers checklist: the headers every site should send
The six security headers every site should send, the value for each, the Mozilla Observatory penalty for a missing one, and how to set them on any host.
LLaunchScaler·Published ·9 min read
Every site should send six security headers: Strict-Transport-Security, Content-Security-Policy, X-Frame-Options (or a CSP frame-ancestors directive), X-Content-Type-Options: nosniff, Referrer-Policy and Permissions-Policy. Mozilla's HTTP Observatory, the free grader MDN runs, deducts 25 points for a missing CSP, 20 for missing HSTS, 20 for missing clickjacking protection and 5 for missing nosniff, from a starting score of 100.
Below is the value to send for each, what the Observatory charges for leaving it out, how to set all of them on Next.js, Vercel, Netlify and Nginx, and a guide for each header that needs more than one line.
Which security headers should every site send?
Send the six headers in the table on every HTML response, over HTTPS, and put an HTTP to HTTPS redirect in front of them. The values below are safe defaults for a typical marketing site or SaaS app; the CSP is the one header you must tailor, because it lists where your scripts come from.
Header
Value to send
What it stops
Observatory if missing or weak
Strict-Transport-Security
Questions, answered
What people ask about this
01
What security headers should a website have?
At minimum: Strict-Transport-Security, Content-Security-Policy, X-Frame-Options or a CSP frame-ancestors directive, X-Content-Type-Options: nosniff, Referrer-Policy and Permissions-Policy. Together they force HTTPS, limit which scripts run, block framing and stop MIME sniffing.
Embedded third parties using powerful browser APIs
Not scored by the Observatory
HTTP to HTTPS redirect
301 or 308 to https:// on the same host
Visitors staying on plain HTTP
Missing: -20
The penalties come from the Observatory's own scoring chart, in the source code MDN publishes. Two optional headers earn bonus points there too: Cross-Origin-Opener-Policy: same-origin and a same-origin or same-site Cross-Origin-Resource-Policy are each worth +10. The redirect row matters as much as any header, because HSTS only takes effect once a browser has reached you over HTTPS; the HTTP to HTTPS redirect guide sets it up as one hop on each host.
How does Mozilla Observatory score security headers?
Every site starts at 100. The Observatory subtracts penalties first; only if the result is 90 or more does it add bonuses, so extra credit cannot rescue a site with a missing CSP. The highest possible score is 145 and the lowest is 0, and the score maps to a letter grade.
Score
Grade
100 and up
A+
90 to 99
A
70 to 79
B
50 to 59
C
30 to 39
D
0 to 24
F
The bands in between carry plus and minus grades (85 to 89 is A-, for example). A site with none of the first four headers in the table loses 25, 20, 20 and 5 points, plus 20 more if it does not redirect to HTTPS, which would leave it at 10: an F. Adding those four headers, with a clean CSP, and the redirect brings the same site back to 100 before any bonus.
MDN is candid that the weights are a judgement call. Its scoring page says the grades are "essentially arbitrary" but based on feedback from industry professionals, and that a header one site needs may be unnecessary for another. Use the score to find what is missing, not as a security rating.
What should Strict-Transport-Security be set to?
Send Strict-Transport-Security: max-age=31536000; includeSubDomains once every subdomain works over HTTPS. The Observatory wants at least 15768000 seconds (six months), the preload list wants at least 31536000 (one year), and browsers ignore the header entirely when it arrives over plain HTTP.
Start lower if you are not sure every subdomain serves HTTPS, because includeSubDomains makes the browser refuse plain HTTP on all of them. Preloading is a separate, hard-to-reverse step. The HSTS header guide gives the value for each stage, testing to production to preload, and the removal risk.
What should your Content-Security-Policy be?
Your CSP should allow only the script, style, image and frame sources your pages actually use, block plugins with object-src 'none', and avoid 'unsafe-inline' in script-src. It is the header that needs the most care, because a policy that is too strict breaks the page and one that is too loose protects nothing.
Roll it out as Content-Security-Policy-Report-Only first and read the violation reports, then switch to the enforcing header. Note that the Observatory scores a report-only policy with no enforcing header the same as no CSP at all (-25). The Content-Security-Policy starter guide has a nonce-based starter policy, the rollout steps and the Next.js setup.
X-Frame-Options or CSP frame-ancestors?
Send both: X-Frame-Options: DENY for older browsers and frame-ancestors 'none' inside your CSP for current ones. MDN points to frame-ancestors for anything beyond deny or same-origin, and the Observatory gives the same +5 for either. The one value to avoid is ALLOW-FROM, which modern browsers ignore.
Use SAMEORIGIN (or frame-ancestors 'self') if your own pages embed each other, and list specific origins in frame-ancestors if a partner must embed you. Neither works from a <meta> tag: MDN says X-Frame-Options in a meta element has no effect, and frame-ancestors is not supported there either. They must be real response headers.
What does X-Content-Type-Options: nosniff do?
It tells the browser to trust the Content-Type your server sends instead of guessing from the file's contents. That stops an uploaded file served as text/plain from being run as JavaScript. The only valid value is nosniff, and the Observatory takes 5 points when it is missing.
Send it on every response, not only HTML: scripts, stylesheets and API responses too. Once it is on, a script served with the wrong type is blocked rather than run, so check that your server labels .js files with a JavaScript MIME type and .css files as text/css.
Which Referrer-Policy and Permissions-Policy values should you use?
Use Referrer-Policy: strict-origin-when-cross-origin, which sends the full URL to your own pages, only the origin to other sites and nothing on an HTTPS to HTTP downgrade. For Permissions-Policy, deny every powerful feature you do not use with an empty allowlist, such as camera=(), microphone=(), geolocation=().
Two details here catch people out:
strict-origin-when-cross-origin is already the browser default when no policy is set, so the header mostly documents intent and earns the Observatory's +5. What matters is not sending a weaker value. The Next.js headers docs use origin-when-cross-origin as their example, which the Observatory scores as unsafe (-5), so copy the value, not the example.
MDN marks Permissions-Policy as not Baseline, because it does not work in some widely used browsers. Send it anyway: it costs nothing where it is ignored and restricts embedded third parties where it is supported. An empty allowlist () disables the feature for your page and every iframe in it.
Which security headers should you remove?
Remove headers that are obsolete or that only help an attacker. OWASP's HTTP Headers Cheat Sheet says not to set X-XSS-Protection (or to set it to 0) and to rely on CSP instead, and to remove Expect-CT and Public-Key-Pins. It also says to remove Server and X-Powered-By or give them non-informative values.
Version strings are the practical one. A header like Server: nginx/1.18.0 or X-Powered-By: Express tells anyone scanning which known vulnerabilities to try. In Nginx, server_tokens off; stops the version appearing in the Server header and on error pages; in Express, app.disable('x-powered-by') drops the header, though Express's own docs note that a determined attacker can still identify the framework.
How do you set security headers in Next.js, Vercel, Netlify and Nginx?
Set them once, at the layer that serves every response: the framework config for Next.js, vercel.json for other frameworks on Vercel, a _headers file on Netlify, and add_header ... always in Nginx. Then check an HTML page, a static asset and a 404, because each platform has a case where headers silently go missing.
Next.js, in next.config.js. Next.js checks these headers before the filesystem, so they also apply to files in /public:
Vercel already redirects HTTP to HTTPS with a 308 and sends Strict-Transport-Security: max-age=63072000; on custom domains by default, for that host only. Add your own Strict-Transport-Security header if you want includeSubDomains.
Netlify, in a _headers file in your publish directory:
Netlify's docs say custom headers do not apply to proxied content or to responses from functions and edge functions, which includes server-rendered pages. Those responses must set their own headers.
Without always, Nginx adds the header only to responses such as 200, 204, 301, 302 and 304, so your error pages go out bare. And a location block with even one add_header of its own inherits none of the ones above it, so check it first when headers vanish on one path.
How do you check your security headers?
Request the page and read the response headers. One command covers the six:
Repeat it for a static file (/favicon.ico) and a URL that returns 404, then run the site through Mozilla's HTTP Observatory at developer.mozilla.org/en-US/observatory for the scored view. The comparison of website security scanners covers what the other free graders read and what none of them can see. In the browser, the Network panel in DevTools shows each response's headers when you click the document request.
Headers are one layer of a site's security. The rest of the checklist lives in these guides:
The mixed content guide finds http:// scripts and images on HTTPS pages, which HSTS does not fix.
The vibe-coded app security checklist covers the failures AI-built apps ship most, from keys in the client bundle to missing row-level security.
Check every header in one scan
To check all of this on a live site at once, run the free scan on LaunchScaler. It needs only your URL and no account, and its 29 security checks read HSTS and its preload eligibility, the CSP and any 'unsafe-inline' or 'unsafe-eval', clickjacking protection, nosniff, Referrer-Policy, Permissions-Policy, cookie flags, CORS, the HTTP to HTTPS redirect, TLS versions and the certificate chain, version-leaking Server headers, exposed /.env and /.git, and the SPF, DKIM, DMARC and CAA records in your DNS. They run with 156 checks across 6 of its 7 categories, all free.
Run curl -sI https://yoursite.com and read the response headers, or test the URL in Mozilla's HTTP Observatory, which scores each header from a baseline of 100. Check an HTML page and an error page, since some servers drop headers on 404s.
03
What does OWASP recommend for security headers?
OWASP's HTTP Headers Cheat Sheet recommends X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Strict-Transport-Security: max-age=63072000; includeSubDomains; preload, a Content-Security-Policy and a Permissions-Policy. It also says not to set X-XSS-Protection, or to set it to 0.
04
Is X-XSS-Protection still needed?
No. OWASP says not to set it or to turn it off with X-XSS-Protection: 0, and to rely on Content-Security-Policy instead.
05
Do security headers affect SEO?
Not directly as ranking signals. HTTPS and a working redirect matter for how Google indexes the site, and the headers protect the visitors and accounts that search traffic brings you.