HTTP security headers are the cheapest browser-side controls a web team can deploy, and most sites still ship them half configured: an allowlist CSP that can be bypassed, HSTS without subdomains, session cookies readable by JavaScript. Each header tells the browser to refuse something it would otherwise allow, such as running an injected script, downgrading to HTTP, rendering inside an attacker's frame or sending a cookie cross-site. This guide covers the headers that matter today, the values to start from, the deprecated headers to remove, and how to roll out Content-Security-Policy and HSTS with report-only modes and a rollback path. It is written for application security engineers, web developers and platform teams who own reverse proxies, CDNs and frameworks.
Key takeaways
- Use a strict CSP, not an allowlist. A nonce or hash policy with
'strict-dynamic',object-src 'none'andbase-uri 'none'resists bypasses that host allowlists do not. - HSTS preload is a one-way door. Ramp
max-ageup in stages and confirm every subdomain serves HTTPS before addingpreload; removal from browser lists takes months. - Session cookies are credentials. Set
Secure,HttpOnly, an explicitSameSitevalue and the__Host-prefix, and sendCache-Control: no-storeon authenticated pages. - Remove headers that hurt. X-XSS-Protection, Expect-CT, Public-Key-Pins and
ALLOW-FROMare deprecated or ignored, and some can create vulnerabilities. - Report before you enforce. Content-Security-Policy-Report-Only, COEP report-only and the Reporting-Endpoints header let you see breakage in production before users do.
How security headers work, and recommended values
Security headers change browser behaviour for the response that carries them. Four rules decide whether they work:
- Headers, not meta tags:
frame-ancestors,sandboxand CSP reporting do not work in a<meta>policy, Content-Security-Policy-Report-Only is not supported in<meta>, and X-Frame-Options in a meta tag has no effect. Deliver them as HTTP headers. - Every response: attackers frame or sniff whichever URL lacks the header, including error pages, redirects and API responses. In nginx,
add_header ... always;is needed for headers to appear on error responses. - HTTPS only for some: RFC 6797 section 8.1 says a browser "MUST ignore any present STS header field(s)" received over insecure transport, and
Securecookies cannot be set byhttp:pages. - Defence in depth, not a fix: the OWASP CSP Cheat Sheet treats CSP as a second layer that does not replace output encoding and other XSS prevention. Headers reduce the impact of a bug; they do not remove it.
The values below come from the OWASP HTTP Headers Cheat Sheet, MDN and web.dev. Treat them as a starting point and test each against your application.
| Header | Starting value | What it prevents | Notes |
|---|---|---|---|
| Content-Security-Policy | script-src 'nonce-{random}' 'strict-dynamic'; object-src 'none'; base-uri 'none' plus frame-ancestors and form-action | Script injection (XSS), plugin abuse, base tag hijack, clickjacking | Deploy as Report-Only first |
| Strict-Transport-Security | max-age=63072000; includeSubDomains; preload | SSL stripping, HTTP downgrade, click-through certificate warnings | Add preload only after the ramp-up |
| X-Content-Type-Options | nosniff | MIME sniffing of uploads and responses into script or HTML | Also send a correct Content-Type with charset=UTF-8 |
| X-Frame-Options | DENY (or SAMEORIGIN) | Clickjacking in older browsers | Keep alongside frame-ancestors |
| Referrer-Policy | strict-origin-when-cross-origin (or no-referrer on sensitive apps) | Leaking paths, tokens and IDs in the Referer header | Set explicitly, do not rely on browser defaults |
| Permissions-Policy | camera=(), microphone=(), geolocation=() and other unused features | Abuse of powerful APIs by injected or embedded code | MDN marks it limited availability |
| Cross-Origin-Opener-Policy | same-origin | Cross-window attacks, XS-Leaks via window.opener | same-origin-allow-popups for popup login or payment flows |
| Cross-Origin-Embedder-Policy | require-corp (or credentialless) | Loading cross-origin resources that did not opt in | Only needed for cross-origin isolation |
| Cross-Origin-Resource-Policy | same-site (or same-origin) | Other sites embedding your resources (Spectre-style reads) | cross-origin for public CDN assets |
| Cache-Control | no-store on authenticated and sensitive responses | Personal data left in shared or browser caches | no-cache still stores, it only revalidates |
| Set-Cookie (session) | __Host-sid=...; Secure; HttpOnly; SameSite=Lax; Path=/ | Cookie theft by script, network or sibling subdomain; CSRF | SameSite=Strict where cross-site entry is not needed |
Deprecated and information-leaking headers
| Header | Status | Action |
|---|---|---|
| X-XSS-Protection | Deprecated, non-standard; MDN warns it "can create XSS vulnerabilities in otherwise safe websites" | Remove, or send 0 as OWASP advises; rely on CSP |
| Expect-CT | Obsolete | Remove (OWASP: do not use) |
| Public-Key-Pins (HPKP) | Removed from major browsers | Remove both HPKP headers; use CT monitoring and CAA records |
X-Frame-Options ALLOW-FROM | Obsolete; whole header ignored | Use frame-ancestors |
| X-Content-Security-Policy, X-WebKit-CSP | Obsolete prefixed CSP headers | Remove; use Content-Security-Policy |
| Feature-Policy | Replaced by Permissions-Policy | Migrate the syntax |
| Server, X-Powered-By, X-AspNet-Version | Information disclosure | Remove or set a generic value; for ASP.NET use enableVersionHeader="false" |
Content-Security-Policy
CSP Level 3 is still a W3C Working Draft (latest dated 16 September 2026), but its core directives are widely implemented. Google's web.dev guidance states that allowlist policies "can be bypassed in most configurations", because a trusted host such as a CDN or analytics domain often serves JSONP endpoints, old libraries or user content an attacker can call. Strict CSP removes host trust from script loading.
Strict CSP: nonces or hashes
Content-Security-Policy:
script-src 'nonce-R4nd0mBase64Value' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none'; base-uri 'none';
frame-ancestors 'none'; form-action 'self';
report-to csp-endpoint
- Nonce-based: for server-rendered pages. The nonce must be cryptographically random (web.dev says ideally 128 bits or more), base64 encoded and regenerated for every response. The OWASP CSP Cheat Sheet warns against middleware that adds the nonce to every script tag, which would also bless injected ones.
- Hash-based: for static or cached pages where a per-response nonce is impractical. List
'sha256-...'values for each inline script; any change, even whitespace, breaks the hash. 'strict-dynamic': lets a trusted script load further scripts it creates, which makes tag managers and widgets workable without listing their hosts.- Fallbacks:
https:and'unsafe-inline'are there only for old browsers. Browsers that support'strict-dynamic'ignore them, and'unsafe-inline'is ignored when a nonce or hash is present. - Refactoring: inline handlers such as
onclickandjavascript:URLs are blocked. Move them toaddEventListenerin script files.'unsafe-hashes'exists for stubborn handlers but weakens the policy.
Directives that matter beyond scripts
| Directive | Group | Recommended use |
|---|---|---|
default-src | Fetch | Fallback for fetch directives not set; 'self' for a non-strict baseline |
object-src | Fetch | 'none' to disable plugins |
connect-src | Fetch | Restrict fetch(), XHR and WebSocket destinations to limit exfiltration |
base-uri | Document | 'none' or 'self' to stop injected <base> tags redirecting relative script URLs |
sandbox | Document | For user-generated or untrusted documents; header only |
frame-ancestors | Navigation | 'none', 'self' or named origins; replaces X-Frame-Options in supporting browsers |
form-action | Navigation | 'self' to stop injected forms posting credentials elsewhere |
upgrade-insecure-requests | Other | Rewrites http: subresources to HTTPS during migrations to remove mixed content |
report-to / report-uri | Reporting | report-uri is deprecated; send both while older browsers remain in use |
Report-only and the Reporting API
Declare endpoints with Reporting-Endpoints (MDN lists it as Baseline 2024; it replaces the older Report-To header) and reference them by name. Non-HTTPS endpoints are ignored.
Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports", default="https://example.com/reports"
Content-Security-Policy-Report-Only: script-src 'nonce-...' 'strict-dynamic'; object-src 'none'; base-uri 'none'; report-to csp-endpoint
You can send an enforced policy and a stricter Report-Only policy at the same time, which is how teams tighten an existing CSP without an outage. Add 'report-sample' to get the first 40 characters of blocked inline code, which makes triage much faster. Trusted Types (require-trusted-types-for 'script') complements strict CSP against DOM XSS where browser support allows.
Strict-Transport-Security and preload
HSTS (RFC 6797, November 2012) tells a browser to use HTTPS for a host for max-age seconds and, under section 8.4, to terminate the connection on any certificate error, so users cannot click through a warning. includeSubDomains extends the rule to every subdomain, and max-age=0 removes the policy. The first visit is still exposed (RFC 6797 calls it the bootstrap MITM vulnerability), which is what preload lists solve.
The hstspreload.org submission requirements are a valid certificate, an HTTP to HTTPS redirect on the same host, HTTPS on all subdomains (including www and internal names with DNS records), and a header on the base domain with max-age of at least 31536000, includeSubDomains and preload, including on HTTPS redirects. The list is built into Chrome and reused by other major browsers.
- Ramp-up: hstspreload.org recommends 300 seconds, then 604800 (one week), then 2592000 (one month), each with
includeSubDomains, waiting the fullmax-ageand fixing breakage at each stage before submitting. - Rollback: before preload, serve
max-age=0to clear cached policies (only browsers that revisit will see it). After preload, "inclusion in the preload list cannot easily be undone"; removal takes months to reach Chrome users and is not guaranteed elsewhere. - Lock-out risk: OWASP warns that a long
max-agecombined with an expired or revoked certificate can lock users out. Certificate automation is a prerequisite. See the Machine Identity, PKI and Certificate Lifecycle Guide.
Framing, MIME sniffing, referrers and browser features
- Clickjacking: CSP
frame-ancestorsis the modern control and supports multiple named origins. KeepX-Frame-Options: DENYorSAMEORIGINfor older clients.ALLOW-FROMis obsolete; MDN notes modern browsers ignore the whole header if they see it. - MIME sniffing:
X-Content-Type-Options: nosniffstops browsers guessing a type, which matters for user uploads. It only works when every response carries the correct Content-Type. - Referrer leakage: URLs carry reset tokens, document IDs and search terms.
strict-origin-when-cross-originsends only the origin cross-site;no-referrersuits admin consoles and health or finance portals. - Permissions-Policy: allowlists are
*,()(disabled everywhere),selfor quoted origins, for examplegeolocation=(self "https://maps.example.com"). An iframeallowattribute can only narrow what the parent header permits. MDN lists 51 directives, includingpayment,usb,display-captureandpublickey-credentials-get. - Permissions policy violations: add
;report-to=endpointto a directive, or define adefaultendpoint, to receivepermissions-policy-violationreports. A Permissions-Policy-Report-Only variant also exists for testing. Violations usually mean an embedded third party is calling an API you disabled, so review them before loosening the policy.
Cross-origin isolation and CORS
COOP, COEP and CORP limit Spectre-class side channels that let other origins read your data. Together, COOP same-origin and COEP require-corp make a page cross-origin isolated (self.crossOriginIsolated returns true), which is required for SharedArrayBuffer and high-resolution timers. Most sites need COOP and CORP, not COEP.
- COOP:
same-originputs the page in its own browsing context group, cuttingwindow.openerlinks to cross-origin windows. Popup-based SSO, OAuth and payment flows that rely onwindow.openercan break;same-origin-allow-popupskeeps popups the page opens. - COEP:
require-corpblocks cross-origin resources that do not send CORP or CORS headers. Test with Cross-Origin-Embedder-Policy-Report-Only first.credentiallessloads them without cookies; web.dev listed it as Chromium-only when written, so check current support. - CORP: set
same-originorsame-siteon authenticated API responses and private assets, andcross-originonly on assets meant for embedding.
CORS is different: it relaxes the same-origin policy rather than adding protection. The common misconfigurations map to CWE-942 (Permissive Cross-domain Security Policy with Untrusted Domains):
- Reflecting the Origin header: echoing any
Originback in Access-Control-Allow-Origin withAccess-Control-Allow-Credentials: truelets any site read authenticated responses. Match against an exact allowlist. - Wildcards with credentials: MDN states browsers block
*for credentialed requests, which pushes developers into reflection. Use*only for public, unauthenticated APIs. - Loose matching: suffix or regex checks that accept
evil-example.comornull. Compare full origins. - Missing
Vary: Origin: when the allowed origin is chosen dynamically, caches can serve one origin's CORS response to another.
Cookies, caching and logout
Session cookies, refresh tokens held in cookies and CSRF tokens are bearer credentials: whoever presents them is the user. MITRE ATT&CK T1539 Steal Web Session Cookie covers their theft, and header settings decide how easily it happens. See the Token and Session Security Guide.
| Attribute or prefix | Effect | Weakness if missing |
|---|---|---|
Secure | Sent only over HTTPS | CWE-614: cookie exposed on any HTTP request |
HttpOnly | Not readable by document.cookie | CWE-1004: XSS can read and exfiltrate the session |
SameSite=Strict / Lax | Withheld on cross-site requests (Lax allows top-level GET navigations) | CWE-1275: CSRF and cross-site leaks |
SameSite=None | Sent cross-site; requires Secure | Only for genuine embedded or federated use |
No Domain | Host-only cookie, not sent to subdomains | A compromised or taken-over subdomain receives the session |
__Host- prefix | Browser rejects it unless Secure, no Domain and Path=/ | Sibling subdomains can overwrite or fixate the cookie |
__Secure- prefix | Must be set with Secure from HTTPS | Weaker than __Host-; use where a Domain is needed |
__Http- / __Host-Http- | Also require HttpOnly, so script cannot set them | Newer prefixes; check browser support before relying on them |
- Do not rely on Lax-by-default: MDN says only "some browsers" default to Lax, with a two-minute POST exception. Set SameSite explicitly.
- Path is not a boundary: MDN states Path "is not intended as a security measure". Separate sensitive apps by host, not path.
- Cache-Control: send
Cache-Control: no-storeon authenticated pages and API responses with personal data, andprivatewhere per-user caching is acceptable. OWASP notesno-cachedoes not prevent storage. - Clear-Site-Data on logout:
Clear-Site-Data: "cache", "cookies", "storage"on the logout response removes local copies. Invalidate the session server-side as well; clearing the browser does not revoke a stolen cookie.
Where to set headers and which options to choose
| Layer | Good for | Trade-off |
|---|---|---|
| Application or framework (Helmet, Spring Security, Django middleware) | CSP nonces, per-route Cache-Control, cookie attributes | Each app team must configure it; drift between services |
| Reverse proxy or ingress (nginx, Envoy, Kubernetes ingress) | HSTS, nosniff, Referrer-Policy, COOP, CORP across many apps | Cannot generate nonces; risk of duplicate headers with the app |
| CDN or edge rules / workers | Organisation-wide baseline, fast rollback, legacy apps | Edge must not cache responses with per-user nonces or cookies |
- Nonce or hash: nonce for dynamic server-rendered apps; hash for static sites and single-page apps served from a CDN.
- Strict CSP or allowlist: choose strict wherever you control the templates. An allowlist with
default-src 'self'and no'unsafe-inline'is an acceptable interim for legacy apps, if no allowed host serves user content or JSONP. - Preload or not: preload when you own the whole domain tree and certificate renewal is automated. Large organisations with forgotten HTTP-only subdomains should fix those first or use HSTS without
includeSubDomainson the apex until they do. - Small vs large: small teams can set the baseline at the CDN or host and check it with Mozilla Observatory; large organisations should publish a header standard and enforce it in ingress templates and CI.
Rolling out without breaking production
- Inventory: scan every hostname, including APIs, login, error pages and redirects, and record current headers. See the Attack Surface Management Guide.
- Quick wins: add nosniff, Referrer-Policy, X-Frame-Options or
frame-ancestors, CORP and cookie attributes, and remove deprecated and banner headers. These rarely break sites. - CSP in report-only: deploy a strict policy as Content-Security-Policy-Report-Only with a reporting endpoint, refactor inline handlers, and watch reports for at least one full release cycle.
- Enforce CSP in rings: enforce on low-risk routes or a percentage of traffic first, keeping the report-only header for the next tightening step.
- HSTS ramp: 300 seconds, one week, one month, then one or two years, with
includeSubDomainsonce all subdomains are confirmed, andpreloadlast. - Cross-origin isolation: only if the app needs it; COEP report-only first, then enforce.
- Rollback: keep header sets in version-controlled configuration with a feature flag per header. CSP and Permissions-Policy roll back instantly; HSTS rolls back only by serving
max-age=0and waiting, and preload barely at all. - Regression tests: assert headers in CI and in synthetic monitoring so a framework upgrade or new proxy cannot silently drop them.
Detection and measurement
CSP and Permissions-Policy reports are a detection source, not just a debugging aid. A sudden burst of script-src violations from one page can be an injection attempt or a compromised third-party script; route reports to the SIEM with page, blocked URI and sample. See the SIEM Guide. Rate-limit and validate the endpoint, because reports are unauthenticated input.
- Coverage: percentage of internet-facing hostnames returning the baseline header set, measured externally.
- Strict CSP enforcement: percentage of applications enforcing a nonce or hash policy without
'unsafe-inline'or'unsafe-eval'. - HSTS: percentage of registrable domains with
max-ageof at least one year andincludeSubDomains, and how many are preloaded. - Cookie hygiene: percentage of session cookies with
Secure,HttpOnly, explicit SameSite and a__Host-prefix. - Violation trend: CSP violations per 10,000 page views by directive, which should fall during refactoring and stay flat after enforcement.
- Regression rate: releases that removed or weakened a header, caught in CI before production.
Common failure patterns
- Trusting a CDN host in
script-src: an allowlisted public CDN serves every package on it. The Lottie Player npm compromise pushed a wallet drainer to websites through a legitimate library, which a host allowlist would not stop. Pin versions, self-host or use Subresource Integrity. - Report-only forever: a Report-Only policy nobody reads is counted as "having CSP".
- Headers on the homepage only: one-URL scans miss login, API and error responses without
frame-ancestorsorno-store. - Preloading too early: an internal subdomain on HTTP or with a private CA becomes unreachable for every browser, and the fix takes months.
- Origin reflection in CORS: a helper that copies the request Origin into the response with credentials enabled exposes every authenticated API to any website.
- Cookies scoped to the parent domain:
Domain=example.comsends the session to every subdomain, including marketing sites and abandoned hosts open to takeover.
Framework and weakness mapping
| Framework | Reference | How headers apply |
|---|---|---|
| NIST CSF 2.0 | PR.DS-02, PR.PS-01, PR.PS-06 | Data in transit (HSTS), configuration management (header baseline), secure development (CSP refactoring) |
| NIST SP 800-53 Rev. 5 | SC-8, SC-23, CM-6 | Transmission confidentiality and integrity, session authenticity, configuration settings |
| ISO/IEC 27001:2022 Annex A | A.8.9, A.8.26, A.8.28 | Configuration management, application security requirements, secure coding |
| CIS Controls v8.1 | Control 3 (Safeguard 3.10), Control 16 | Encrypt sensitive data in transit; application software security |
| MITRE ATT&CK | T1539 Steal Web Session Cookie | HttpOnly, Secure, __Host- and short sessions raise the cost of cookie theft |
| MITRE CWE | CWE-614, CWE-1004, CWE-1275, CWE-942, CWE-1021 | Cookie flags, SameSite, permissive CORS or CSP, clickjacking (rendered UI layers) |
For how these frameworks relate to each other, see the Control Mapping Guide.
Practitioner checklist
- Inventory headers on every internet-facing hostname, including login, API, redirect and error responses.
- Deploy a nonce or hash CSP with
'strict-dynamic',object-src 'none',base-uri 'none',frame-ancestorsandform-action, in report-only first. - Stand up a Reporting-Endpoints collector, keep
report-urifor older browsers, and send reports to the SIEM. - Ramp HSTS to at least one year with
includeSubDomains; preload only when every subdomain is HTTPS and certificates renew automatically. - Set session cookies as
__Host-,Secure,HttpOnlyand explicitSameSite, with noDomainattribute. - Send
Cache-Control: no-storeon authenticated responses andClear-Site-Dataplus server-side session revocation on logout. - Add nosniff, Referrer-Policy, Permissions-Policy for unused features, COOP and CORP.
- Replace CORS origin reflection with an exact allowlist, add
Vary: Origin, and never combine*with credentials. - Remove X-XSS-Protection, Expect-CT, HPKP, Server and X-Powered-By values that disclose versions.
- Assert the header baseline in CI and synthetic monitoring so regressions fail the build.
Standards and references
- W3C: Content Security Policy Level 3
- IETF: RFC 6797 HTTP Strict Transport Security (HSTS)
- OWASP: HTTP Security Response Headers Cheat Sheet
- OWASP: Content Security Policy Cheat Sheet
- OWASP: Secure Headers Project
- MDN: Set-Cookie header
- web.dev: Mitigate cross-site scripting with a strict Content Security Policy
- Chromium: HSTS Preload List Submission
Related NHI Mgmt Group resources: Token and Session Security Guide · Enterprise Browser and Browser Security Guide · Open Source Security Guide · TLS Inspection Guide · Attack Surface Management Guide