HTTP security headers explained: HSTS, CSP and more

Updated 2026-10-26 ยท Draft, not yet reviewed

Response headers tell the browser how to treat a site: whether to insist on HTTPS, where scripts may come from, who may show the page in a frame and how much of the address to pass on to other sites. They do not change the page, but they limit what an attacker can do with it. The security headers check follows a site's redirects and reads these headers from the final HTTPS response.

HTTP to HTTPS

Visitors who type a bare name, or follow an old link, start on http://. The server should answer with a permanent redirect (301 or 308) to the same path on https://. Without it they stay on a connection that anyone on the path can read and change: HTTP_NO_HTTPS_REDIRECT. The check reports this whenever the HTTP chain does not end on HTTPS, whatever the redirect status.

A redirect alone still leaves the first request unencrypted, and an attacker can intercept it. That is what HSTS closes.

Strict-Transport-Security (HSTS)

RFC 6797 defines a header that tells the browser to use only HTTPS for the host for max-age seconds:

Strict-Transport-Security: max-age=31536000; includeSubDomains
  • max-age is required. A header without a valid max-age, or with a directive repeated, is ignored by the browser, and the check counts it as missing (HSTS_MISSING).
  • includeSubDomains extends the rule to every subdomain.
  • The browser processes only the first Strict-Transport-Security header of a response, and only over HTTPS.

Roll it out in steps: start with a few minutes or a day, check that nothing breaks, and then raise the value. One year (31 536 000 seconds) is the usual end state; below that the check gives HSTS_SHORT_MAX_AGE as information.

Preloading

HSTS works from the second visit. To cover the first one too, browsers ship a list of preloaded domains, managed through hstspreload.org. Its requirements are a valid certificate, a redirect from HTTP to HTTPS on the same host if port 80 answers, HTTPS on all subdomains, and an HSTS header on the base domain with max-age of at least 31 536 000, includeSubDomains and preload. HSTS_NOT_PRELOAD_READY lists what is missing. Removal from the list takes months to reach users, so preload only domains that will stay HTTPS-only everywhere.

Content Security Policy

A Content Security Policy (W3C CSP Level 3) lists where a page may load scripts, styles, images and frames from. Its main use is to make cross-site scripting much harder: an injected script that does not come from an allowed source does not run.

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0m'; frame-ancestors 'self'
  • script-src controls scripts; without it, default-src applies.
  • 'unsafe-inline' allows any inline script and removes most of the protection. With a nonce or hash in the same directive, browsers that support CSP Level 2 or later ignore it. 'unsafe-eval' allows eval() and is not made safe by a nonce. Both cases give CSP_UNSAFE_INLINE.
  • Content-Security-Policy-Report-Only sends reports but blocks nothing. It is the safe way to try a policy first, but it does not count as protection (CSP_MISSING).

Whether a site needs a strict policy depends on what it does, which is why the check reports CSP findings as information.

Frame protection

A page shown inside another site's frame can be overlaid with misleading buttons, so that a visitor clicks something they did not mean to (clickjacking). frame-ancestors in a Content Security Policy, or the older X-Frame-Options: DENY or SAMEORIGIN, prevents that. With neither, the check reports FRAME_PROTECTION_MISSING.

Other headers

  • X-Content-Type-Options: nosniff stops browsers from guessing a response's type from its content (XCTO_MISSING).
  • Referrer-Policy decides how much of the page address is sent to other sites. Current browsers default to strict-origin-when-cross-origin; setting it makes the behaviour the same everywhere (REFERRER_POLICY_MISSING).
  • Permissions-Policy turns off browser features the site does not use, such as the camera or geolocation.
  • Cross-Origin-Opener-Policy: same-origin separates the page from windows opened by or opening other sites.

Cookies

A cookie without the Secure attribute is also sent over plain HTTP, where it can be read (COOKIE_NOT_SECURE). HttpOnly keeps a cookie away from JavaScript, and SameSite=Lax or Strict keeps it off most cross-site requests. Session cookies should have all three.

A safe order

  1. Serve every page over HTTPS with a valid certificate (see the TLS certificates guide).
  2. Redirect HTTP to HTTPS permanently.
  3. Add X-Content-Type-Options, frame protection and a Referrer-Policy.
  4. Add HSTS with a short max-age, then raise it to a year.
  5. Write a Content Security Policy, run it as Report-Only, fix what it reports and then enforce it.
  6. Consider preloading only when every subdomain is HTTPS-only.

HTTP findings

HTTPS findings

Redirects findings

HSTS findings

Content Security Policy findings

X-Content-Type-Options findings

Frame protection findings

Referrer-Policy findings

Frequently asked questions

Which security headers matter most?

A redirect from HTTP to HTTPS and a Strict-Transport-Security header matter for nearly every site, because they keep visitors on encrypted connections. X-Content-Type-Options: nosniff and frame protection are cheap and rarely cause problems. A Content Security Policy gives the most protection against cross-site scripting, but it needs the most work to get right.

Is HSTS preloading a good idea?

Only when every subdomain works over HTTPS and will keep doing so. Preloaded domains are built into browsers, so a mistake cannot be undone quickly: removal takes months to reach users. Check every subdomain first, then send includeSubDomains with a short max-age, raise the max-age in steps, and add preload last.

Why is 'unsafe-inline' in a CSP a problem?

Most cross-site scripting injects inline script. A policy that allows 'unsafe-inline' for scripts lets that script run, so it gives little protection. With nonces or hashes, browsers that support CSP Level 2 or later ignore 'unsafe-inline' and run only the scripts you marked. The security headers check reports the unsafe case as CSP_UNSAFE_INLINE.

Do I still need X-Frame-Options?

The frame-ancestors directive of Content Security Policy replaces it, and browsers that support both follow frame-ancestors. X-Frame-Options is still useful as a fallback for old browsers, and it is easier to add when there is no CSP yet.