Skip to content

Technical guide

HTTP security headers

Response headers tell the browser how to treat your pages: HTTPS only, which scripts may run, who may frame them and what to share with other sites. Most are one line of server configuration; the Content Security Policy needs more care.

Reviewed October 2026.

At a glance

HeaderA sensible starting valueCare needed
Strict-Transport-Securitymax-age=31536000; includeSubDomainsOnly once every subdomain serves HTTPS
Content-Security-PolicyDepends on the siteHigh: run it in report-only mode first
X-Content-Type-OptionsnosniffLow
Framing controlContent-Security-Policy: frame-ancestors 'self'List any sites that embed your pages
Referrer-Policystrict-origin-when-cross-originLow
Permissions-Policycamera=(), microphone=(), geolocation=()Allow the features your pages use
Set-CookieSecure; HttpOnly; SameSite=Lax on session cookiesA cookie that scripts must read cannot be HttpOnly
Server, X-Powered-ByNo version numbersLow

Set headers where they reach every response: the web server, the application framework or the CDN. Check error pages and redirects as well as normal pages. On nginx, for example, add_header skips error responses unless you add always, and a block with its own add_header lines drops the ones inherited from the level above.

HSTS, framing control and a report-only CSP work only as real HTTP headers. Browsers ignore them in an HTML <meta> tag.

Strict-Transport-Security (HSTS)

HSTS (RFC 6797) tells a browser to use only HTTPS for your site for max-age seconds. Browsers ignore the header on plain HTTP responses, so send it over HTTPS and redirect HTTP to HTTPS separately. While the policy lasts, a certificate warning on your site cannot be clicked through.

  • max-age=31536000 is one year. Ironfang reports a max-age under six months, because the protection lapses for occasional visitors.
  • includeSubDomains extends the policy to every subdomain. Add it only when every subdomain, including internal tools and old services, serves valid HTTPS.
  • If unsure, ramp up: hstspreload.org suggests five minutes, then a week, then a month, before a long max-age.
  • max-age=0 withdraws the policy, but only for browsers that receive it over HTTPS.

Preloading

HSTS cannot protect a browser's first visit, before it has seen the header. The preload list closes that gap: browsers ship with a list of domains that are HTTPS-only from the start. To join, hstspreload.org requires a valid certificate, a redirect from HTTP to HTTPS on the same host, HTTPS on every subdomain, and a header with a max-age of at least one year, includeSubDomains and preload. Removal takes months to reach users, so preload only when every name under the domain will stay on HTTPS.

  • HSTS checker Check Strict-Transport-Security: max-age, includeSubDomains, preload and the HTTP redirect.

Findings and fixes are in the HSTS check reference. The redirect and certificates behind HSTS are covered in TLS and certificates.

Content-Security-Policy

A Content Security Policy tells the browser which scripts, styles, frames and other resources a page may load and run. Its main job is to limit the damage of cross-site scripting: an injected script the policy does not allow does not run. It is a second line of defence, not a fix for the bug that let the script in.

There is no single correct value, because the policy has to match what your pages load. MDN recommends a strict policy based on nonces or hashes rather than a list of allowed hosts, because host lists are hard to get right and often allow more than intended. A nonce-based strict policy:

Content-Security-Policy: script-src 'nonce-{random}'; object-src 'none'; base-uri 'none'

  • The server generates a new random nonce for every response and adds it to each legitimate <script> tag. A static site can list hashes of its scripts instead.
  • 'strict-dynamic' lets a trusted script load further scripts, which helps with tag managers and third-party widgets.
  • 'unsafe-inline' in script-src without a nonce or hash removes most of the protection. Ironfang reports it.
  • frame-ancestors controls who may frame the page; see framing control below.

For a simple site with no third-party scripts, default-src 'self' plus the extra sources you need is a reasonable first policy. Either way, start with Content-Security-Policy-Report-Only: the browser reports what it would have blocked without blocking it. Send the reports to an endpoint with report-to, fix or allow what is legitimate, and enforce once the reports are quiet.

Findings and fixes are in the Content Security Policy check reference.

X-Content-Type-Options

X-Content-Type-Options: nosniff stops browsers guessing a response's type from its content. A script or stylesheet served with the wrong type is blocked, and other responses are treated as the type the server declared. Send it on every response, and make sure every response carries a correct Content-Type.

Findings and fixes are in the X-Content-Type-Options check reference.

Framing control

Clickjacking loads your page in an invisible frame on another site and tricks visitors into clicking it. Control who may frame your pages with the CSP frame-ancestors directive.

  • frame-ancestors 'none': nobody, including your own site.
  • frame-ancestors 'self': your own origin only.
  • frame-ancestors 'self' https://partner.example: your origin and the sites you name.

X-Frame-Options is the older header, with DENY and SAMEORIGIN; its ALLOW-FROM value is obsolete and modern browsers ignore it. frame-ancestors supersedes it, but sending both does no harm and covers older browsers.

Findings and fixes are in the clickjacking protection check reference.

Referrer-Policy

When a visitor follows a link or a page loads a resource, the browser may send the page's address in the Referer header, and full URLs can carry search terms, tokens or identifiers. strict-origin-when-cross-origin sends the full URL within your own site, only the origin to other HTTPS sites, and nothing to plain HTTP. It is the browser default when no policy is set; setting it explicitly keeps it fixed. no-referrer and same-origin share less. Avoid unsafe-url, which sends full URLs everywhere.

Findings and fixes are in the Referrer-Policy check reference.

Permissions-Policy

Permissions-Policy switches off browser features your pages do not use, such as the camera, microphone and location, so that injected or third-party code cannot use them either. camera=() disables a feature everywhere; geolocation=(self) allows it on your own origin only. Browser support is uneven, and MDN marks the header experimental, so treat it as a cheap extra layer rather than a control to rely on.

Findings and fixes are in the Permissions-Policy check reference.

Cookies

Cookie attributes are set in the Set-Cookie header, one cookie at a time. For session and authentication cookies:

  • Secure: sent only over HTTPS. Set it on every cookie a site served over HTTPS sets.
  • HttpOnly: not readable from JavaScript, so a cross-site scripting bug cannot read the session. Leave it off only for cookies a script genuinely needs.
  • SameSite=Lax or Strict: limits when the cookie is sent with requests started by other sites, which protects against cross-site request forgery. SameSite=None sends it everywhere and requires Secure. Browsers differ in how they treat a cookie without the attribute, so set it explicitly.
  • No Domain attribute unless subdomains need the cookie. Without it the cookie goes only to the host that set it; with it, every subdomain receives it.
  • The __Host- name prefix makes the browser enforce this: such a cookie must be Secure, have Path=/ and have no Domain.

A session cookie set this way: Set-Cookie: __Host-session=<value>; Secure; HttpOnly; SameSite=Lax; Path=/

Findings and fixes are in the cookie security check reference.

Version disclosure and retired headers

Server and X-Powered-By often announce the exact software and version behind a site, which lets automated tools match it against known vulnerabilities. Remove the version: server_tokens off; in nginx, ServerTokens Prod and ServerSignature Off in Apache, expose_php = Off in PHP, and turn off X-Powered-By in your framework. This removes a free signal but fixes nothing; keeping the software supported and updated matters far more.

X-XSS-Protection switched on a cross-site scripting filter in older browsers. It is deprecated, and MDN notes that it can create vulnerabilities in otherwise safe sites. Do not add it; if something already sends it, OWASP recommends X-XSS-Protection: 0. A Content Security Policy is the replacement.

Findings and fixes are in the server information disclosure check reference.

Checking your headers

A check sees the responses to the requests it makes. Headers set in application code can differ from page to page, so also look at the login page, application routes and error pages in your browser's developer tools.

What a check can show, and what it cannot

Ironfang can check

  • Which security headers a response carries, and their values
  • The HSTS max-age, and whether a CSP allows unsafe inline scripts
  • The attributes of cookies the site sets
  • Software versions disclosed in headers

The organisation must establish

  • That the headers apply to every page, including signed-in areas a check cannot reach
  • Which third-party scripts the site needs, and who decides to add one
  • Who reads the CSP reports
  • That the software behind any disclosed version is supported and updated

The External Security Check runs the header checks on a verified domain with the DNS, email and TLS checks, keeps the results with the domain's history, and can recheck on a schedule.

Sources

Checked on the review date above. Standards and schemes change; the source is the authority.