Every response from your site carries headers the visitor never sees. Some of them are security instructions for the browser: load scripts only from these places, use HTTPS for this host, do not let other sites frame this page, keep this cookie away from JavaScript. When they are missing, the browser falls back to its more permissive defaults. When they are wrong, sign-in or checkout can break.
This guide covers how to read the headers your site actually sends, what each one does and which values are safe to start with. It includes a baseline configuration for nginx and for static hosts such as Cloudflare Pages and Netlify, and the mistakes that leave some responses without headers. All examples use example.com.
Scope of this guide
The free Launch Readiness Snapshot reads the headers of one response: the home page of the origin you enter, after up to five redirects. It mostly checks whether each header is present, not whether the values suit your site, and it is not a pentest.
1. Check your headers yourself: DevTools and curl
In Chrome or Edge, open DevTools, select the Network panel and reload the page. Click the first request (the HTML document), open the Headers tab and scroll to Response Headers. Tick Preserve log to keep requests across page loads and redirects, and Disable cache to get a fresh response instead of a cached one (Chrome DevTools network reference). The Network Monitor in Firefox works the same way.
From a terminal, curl prints exactly what the server sends:
curl -sS -D - -o /dev/null https://example.com/Add -L to follow redirects and see the headers of every hop. On Windows, call curl.exe and use -o NUL. curl -I is shorter, but it sends a HEAD request instead of GET, and some applications handle HEAD on a different code path, so confirm what you find with a GET.
Repeat the check for the http:// address (it should answer with a 301 or 308 redirect to HTTPS), the other host name (www or the bare domain), a page that does not exist, a sign-in page, an API response and a static file. The application, the web server and the CDN often set different headers on each of these, and only what reaches the browser counts.
2. What each header does and a safe value to start with
| Header | What it does | Safe starting value |
|---|---|---|
| Content-Security-Policy | Limits where scripts, styles, frames and other resources may load from | A draft policy, sent first as Content-Security-Policy-Report-Only |
| CSP frame-ancestors | Decides which sites may embed the page in a frame (clickjacking defense) | frame-ancestors 'self' or 'none' |
| X-Frame-Options | Legacy framing control, kept as a fallback for older browsers | SAMEORIGIN or DENY, matching frame-ancestors |
| Strict-Transport-Security | Tells the browser to use only HTTPS for this host for max-age seconds | max-age=300, raised in stages |
| X-Content-Type-Options | Stops MIME-type guessing; blocks scripts and stylesheets served with the wrong type | nosniff |
| Referrer-Policy | Controls how much of the page URL is sent to other sites in the Referer header | strict-origin-when-cross-origin |
| Permissions-Policy | Switches off browser features such as the camera for the page and its frames | camera=(), microphone=(), geolocation=() |
| Cross-Origin-Opener-Policy | Isolates your window from cross-origin pop-ups and from the page that opened it | same-origin, or same-origin-allow-popups for OAuth or payment pop-ups |
| Set-Cookie attributes | Limit how session cookies travel and who can read them | Secure; HttpOnly; SameSite=Lax |
| X-XSS-Protection | Controlled a filter in old browsers; now deprecated | Remove it, or send 0 |
With nosniff, the browser trusts the declared Content-Type and blocks scripts and stylesheets that arrive with the wrong one, so check those types before you switch it on. Without a Referrer-Policy header, current browsers already use strict-origin-when-cross-origin; OWASP still suggests sending it explicitly for older browsers, and no-referrer is stricter if nothing you rely on needs the referrer. In Permissions-Policy, () disables a feature in the page and in every frame inside it; allow a feature only for the origin of the widget that needs it. COOP same-origin helps against cross-origin leak attacks (XS-Leaks) but can break sign-in or payment pop-ups served from another origin.
3. Content-Security-Policy and frame-ancestors
CSP tells the browser where a page may load scripts, styles, images, frames and connections from. It limits what injected script can do; it does not replace escaping and validating input. The MDN CSP guide recommends a strict CSP built on a fresh nonce per response, or on hashes, rather than a long list of allowed hosts. 'unsafe-inline' defeats much of the purpose, and the browser ignores it in any directive that also contains a nonce or hash. What makes a strict policy hard is usually third-party code that loads further scripts, such as tag managers and chat widgets, so write down the reason for every exception you add.
default-src is the fallback for fetch directives such as script-src, but not for frame-ancestors: a policy of default-src 'none' still lets any site frame the page. Set frame-ancestors 'self' explicitly, or 'none', which is similar to X-Frame-Options: DENY (MDN). The OWASP HTTP Headers Cheat Sheet says frame-ancestors makes X-Frame-Options obsolete in browsers that support it, while X-Frame-Options still covers older browsers; if you send both, make them allow the same framing. Two traps: frame-ancestors, a report-only policy and X-Frame-Options have no effect in a <meta> tag, and X-Frame-Options: ALLOW-FROM makes modern browsers ignore the whole header.
A first draft for a site without third-party scripts is the recommended policy of the OWASP Secure Headers Project:
default-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requestsIt blocks inline scripts and styles and anything loaded from other hosts, so expect many reports at first; that is why it starts as Report-Only (section 8). Take only the CSP line: the project’s full set also contains values such as Cache-Control: no-store and Clear-Site-Data, which would disable caching and clear the visitor’s cookies and stored data on every response. On JSON API responses, which the browser does not render, CSP does little.
4. HSTS: ramp up max-age and think twice about preload
Strict-Transport-Security tells the browser to use only HTTPS for the host and to upgrade later http:// requests automatically. Browsers ignore the header when it arrives over plain HTTP, so send it on HTTPS responses, not on the HTTP-to-HTTPS redirect. On its own, it cannot protect a visitor’s very first connection.
Plan for the cost: on an HSTS host, visitors cannot click through certificate errors, so an expired certificate locks out returning visitors. Public TLS certificates issued since 15 March 2026 may be valid for at most 200 days (CA/Browser Forum), and Let’s Encrypt no longer emails expiry reminders, so put automated renewal and your own expiry monitoring in place first. The SSL/TLS and security headers guide covers the certificate side.
Raise max-age (in seconds) in stages, as hstspreload.org recommends, and check for breakage at each step: 300 (5 minutes), 604800 (1 week), 2592000 (1 month), then 31536000 (1 year) or 63072000 (2 years, the value OWASP uses). max-age=0 removes the policy, but only when sent over HTTPS, and only for visitors who come back and receive it. Add includeSubDomains once every subdomain works over HTTPS, and have each subdomain send its own header as well.
Preload builds your domain into browsers so that even the first visit uses HTTPS. The submission site, hstspreload.org, now recommends HSTS but not preloading: Chrome and Safari already upgrade HTTP navigations to HTTPS, so preloading adds little, and a removal from the list takes months to reach users. The OWASP HTTP Headers Cheat Sheet still includes preload in its example header, while the OWASP Secure Headers Project’s recommended value leaves it out. Do not add it by default or copy it from a template.
6. Headers to drop: X-XSS-Protection and other leftovers
- X-XSS-Protection switched on a filter in old browsers that could itself create XSS vulnerabilities (MDN). OWASP says not to set it, or to turn it off with
0; CSP is the replacement. - Expect-CT: OWASP says not to use it and, citing Mozilla, to remove it from existing configurations.
- Public-Key-Pins (HPKP) was removed from Chromium in 2018 and is not supported by any modern browser.
- Feature-Policy has been replaced by Permissions-Policy.
Server and X-Powered-By name your software, sometimes with version numbers. OWASP suggests removing them or making them uninformative, but its own testing guide calls this security through obscurity, and MDN says keeping software patched matters more. Remove the version numbers (in nginx, server_tokens off; removes the version, not the header) and spend the effort on updates.
7. A baseline to copy for nginx and static hosts
This nginx baseline sets the low-risk headers, starts HSTS and CSP in their test stages and redirects HTTP to HTTPS on the same host. The always parameter adds the headers to error responses too.
server {
listen 80;
server_name example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# ssl_certificate and ssl_certificate_key go here
server_tokens off;
add_header Strict-Transport-Security "max-age=300" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Content-Security-Policy-Report-Only "default-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'" always;
}Two documented behaviors of the nginx headers module explain many missing headers. Without always, add_header applies only to status codes 200, 201, 204, 206, 301, 302, 303, 304, 307 and 308, so 404 and 500 pages go out without the headers. And add_header directives are inherited from the enclosing level only if the current level defines none: a single add_header inside a location block drops every server-level security header there. Repeat the headers, include a shared file, or, from nginx 1.29.3, use add_header_inherit merge;.
Static hosts such as Cloudflare Pages and Netlify read a _headers file deployed with the site. This example follows Cloudflare’s documented format, which Netlify uses as well:
/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Frame-Options: SAMEORIGIN
Content-Security-Policy-Report-Only: default-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'Know the limits. Cloudflare Pages does not apply the file to responses generated by Pages Functions, allows up to 100 header rules with at most 2,000 characters per line, and joins the values with a comma when the same header is applied twice. Netlify applies custom headers only to files it serves itself, not to proxied content, functions, edge functions or server-rendered pages; those responses must set their own headers. HSTS is left out of the example: check with curl what the platform already sends before you add it.
8. Roll out in stages and avoid the common mistakes
- Add nosniff, Referrer-Policy, Permissions-Policy and framing protection, then test sign-in, checkout and embedded widgets.
- Send the draft CSP as
Content-Security-Policy-Report-Only. Nothing is blocked; violations appear in the browser console, and at a reporting endpoint if the policy names one. Walk through the key journeys, including pages behind sign-in, and adjust the policy. - Start HSTS with a short
max-ageand raise it in stages. - Enforce the CSP. A stricter Report-Only policy can run alongside it to test the next tightening.
- Re-run the curl checks after every server, CDN or framework change.
Common mistakes:
- Headers on some responses only. The home page has them; the 404 page, the API, static files or a separately served login page do not.
- Changing host and scheme in one redirect. If
http://example.comredirects straight tohttps://www.example.com, the browser never receives HSTS forexample.com. Redirect to HTTPS on the same host first, send HSTS on that HTTPS response, and only then redirect towww. - The CDN overrides the origin. A CDN or proxy can add, replace, strip or duplicate headers. Decide which layer owns each header and confirm the result with curl.
- A CSP that allows everything. A policy padded with
'unsafe-inline','unsafe-eval'or*to silence errors passes a presence check but blocks little. - includeSubDomains too early. A forgotten subdomain that only works over HTTP becomes unreachable for browsers that have stored the policy.
Headers are one layer. The website launch checklist puts them next to DNS, TLS and redirects, and the website security audit guide covers sign-in, permissions and a repeatable review routine.
9. How the free Launch Readiness Snapshot checks headers
The free Launch Readiness Snapshot runs eight passive checks on one public origin without signup; one of them reads HTTP security headers. It is not a pentest and not the six-area audit.
- It requests the home page of the origin you enter (paths and query strings are dropped), follows up to five redirects and judges the final response. No other pages are requested.
- It mostly checks presence. A missing CSP, missing HSTS on an HTTPS target, no framing protection and
Access-Control-Allow-Origin: *are rated medium. X-Content-Type-Options other thannosniff, a missing Referrer-Policy and anyServerorX-Powered-Byheader are rated low, soServer: nginxwithout a version still raises a low signal. Cookies set on that response without HttpOnly, or without Secure on HTTPS, are rated medium; SameSite is not checked. - A separate hardening grade (A to F) scores the headers present together with the TLS result, and gives CSP less credit when it allows
'unsafe-inline'(or'unsafe-eval'for scripts). Permissions-Policy and COOP count only in this grade. - The snapshot’s separate HTTPS redirect check depends on what you enter. For a bare domain or an
https://address, it flags an HSTSmax-ageunder 180 days as low, which is expected during a ramp-up, and looks forhttp://references in the page HTML (mixed content); it does not test the HTTP-to-HTTPS redirect. The redirect is tested only when you enter thehttp://address, and that run skips the TLS check and does not report missing HSTS.
The result shows one score, the hardening grade, the number of risk signals and passed checks, and up to three signals, risks first. It does not list every header and its value, so use DevTools or curl for that and for the responses the snapshot does not request.
Common questions
How do I check the security headers of my website?
In browser DevTools, open the Network panel, reload the page, select the HTML document and read Response Headers. In a terminal, curl -sS -D - -o /dev/null https://example.com/ prints them. Check the http:// address, a 404 page, an API response and a static file too, because their headers often differ.
Which HTTP security headers should I set first?
Start with X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy and framing protection, which are the least likely to break anything; still test scripts, sign-in and embedded widgets afterwards. Then test a Content-Security-Policy in Report-Only mode and raise HSTS max-age in stages.
Should I still send X-XSS-Protection?
No. Remove it or send X-XSS-Protection: 0, and rely on a Content-Security-Policy instead. The old browser filter it controlled could itself create XSS vulnerabilities.
Do I need X-Frame-Options if I already use frame-ancestors?
The CSP frame-ancestors directive replaces it in browsers that support it. X-Frame-Options: DENY or SAMEORIGIN is still a reasonable fallback for older browsers, as long as both headers allow the same framing. Do not use ALLOW-FROM: modern browsers ignore the whole header when they see it.
Should I add my domain to the HSTS preload list?
Usually not. The preload submission site, hstspreload.org, now recommends HSTS but not preloading, because Chrome and Safari already upgrade HTTP navigations to HTTPS. A removal from the list also takes months to reach users.
Do security headers make my site secure?
Not on their own. They limit the damage from injected scripts, clickjacking, protocol downgrades and cookie theft. They do not fix vulnerable code, outdated software or weak access control.
Sources & further reading
- MDN: Content Security Policy (CSP) guidedeveloper.mozilla.org
- MDN: CSP frame-ancestors directivedeveloper.mozilla.org
- MDN: Strict-Transport-Security headerdeveloper.mozilla.org
- MDN: Set-Cookie header and cookie attributesdeveloper.mozilla.org
- OWASP HTTP Headers Cheat Sheetcheatsheetseries.owasp.org
- OWASP Secure Headers Project: recommended header valuesraw.githubusercontent.com
- HSTS Preload List Submissionhstspreload.org
- nginx: ngx_http_headers_module (add_header)nginx.org
- Cloudflare Pages: Headersdevelopers.cloudflare.com
- Netlify: Custom headersdocs.netlify.com
Prepared by the Sitelemetry editorial team. Explore the linked sources for further detail.



