Domain.Posture
← blog

Security headers every site should have in 2026

· security · http · headers · csp · hsts

securityhttpheaderscsphsts

Six HTTP response headers handle 90% of the browser-enforced security surface for a modern web application. Sending them correctly is cheap; sending them wrong is subtle. Here is the 2026 baseline.

Strict-Transport-Security

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Tells browsers to upgrade every request to this domain (and subdomains) to HTTPS for two years. After the first hit, http-only links never reach the network — the browser rewrites them.

The preload directive is an opt-in to the HSTS preload list compiled into Chrome, Firefox, Safari, and Edge. Once preloaded, the upgrade applies even for never-visited-before traffic. Submit at hstspreload.org. Reversal takes months, so test includeSubDomains carefully first.

Content-Security-Policy

The hardest header to get right. A starter policy for a modern SPA:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-<request-nonce>';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' data:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;

Key principles:

  • default-src 'self' makes everything same-origin by default. You whitelist exceptions.
  • 'nonce-...' generated per-request is the modern replacement for 'unsafe-inline'. Every <script> tag echoes the same nonce.
  • frame-ancestors 'none' is the CSP-level replacement for X-Frame-Options: DENY. Send both during transition; CSP wins in modern browsers.
  • report-uri (or the newer report-to) sends violations as JSON to a collector. Deploy in report-only mode (Content-Security-Policy-Report-Only) for a week before enforcing.

X-Content-Type-Options

X-Content-Type-Options: nosniff

Disables MIME sniffing. Without it, a browser that receives a file served as text/plain but starting with <script> may execute it as JS. The fix is one header. There is no reason not to send it.

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Default in modern Chrome/Firefox. Sends the full URL to same-origin requests, only the origin to cross-origin HTTPS requests, and nothing downgrading to HTTP. Prevents leaking private URL paths to third-party analytics, ad networks, and CDNs.

Permissions-Policy

The successor to Feature-Policy. Gates access to powerful browser APIs:

Permissions-Policy: camera=(), microphone=(), geolocation=(), browsing-topics=()

Each empty list means "no origin, not even self, may call this API on this document." If your app genuinely needs one, allow it: camera=(self). The important disables in 2026 are browsing-topics (Chrome's replacement for third-party cookies for ad targeting) and any of the sensor APIs you do not explicitly use.

X-Frame-Options

X-Frame-Options: DENY

Legacy but still respected. Send it in addition to frame-ancestors 'none' to cover old browsers. Only drop it when analytics say your traffic is exclusively on browsers that support CSP Level 2+ (i.e. everyone in 2026).

What to send, minimally

A single response should carry all six. Configure them in one edge function or middleware so you don't have to patch each route individually. Re-verify after any infra change that touches the response path.

Inspect a domain's response headers →

Further reading

Frequently asked questions

What does the Strict-Transport-Security header do?
HSTS forces browsers to upgrade all requests to your domain to HTTPS for a set period (typically 2 years). After the first hit, the browser rewrites http-only links before they reach the network, preventing downgrade attacks.
Why is X-Content-Type-Options: nosniff important?
It disables MIME sniffing, preventing browsers from executing files served as plain text if they contain script tags. There is no reason not to send it.
What is the purpose of Permissions-Policy?
Permissions-Policy gates access to powerful browser APIs like camera, microphone, and geolocation. Setting them to empty (e.g. `camera=()`) means no origin can call that API, reducing attack surface.