HomeBlog › Security headers

The security headers that actually matter

Six response headers, most of them one line each, that tell a browser how carefully to treat your site. They are among the cheapest protections available and among the most commonly absent.

HTTP security headers illustration: layered translucent panels in front of a browser window, each labelled with a header and what it does, covering Strict-Transport-Security, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy and Permissions-Policy.

HTTP security headers are instructions your server attaches to every response, telling the browser how cautious to be with your site. They cost nothing at runtime, most are a single line of configuration, and they are absent from a great many sites simply because nobody has gone through the list.

There are more than six in existence. These are the six worth the configuration.

Strict-Transport-Security (HSTS)

Strict-Transport-Security: max-age=31536000; includeSubDomains

Tells the browser to use HTTPS for your domain for the next year, without trying HTTP first. This closes the gap where someone typing your domain gets one unencrypted request before the redirect - a request that can be intercepted on a hostile network.

Two cautions. includeSubDomains covers every subdomain, so confirm they all serve HTTPS before publishing it. And the policy is hard to retract: the browser honours it for the full max-age, so a site that later cannot serve HTTPS becomes unreachable rather than insecure. Start with a shorter max-age if you want an easier exit.

Content-Security-Policy (CSP)

Content-Security-Policy: default-src 'self'; img-src 'self' data:;
  style-src 'self' 'unsafe-inline'; script-src 'self'; frame-ancestors 'self'

The most powerful of the six and the only one that can break your site. CSP declares where resources may load from, so an injected script from an origin you never approved simply does not execute.

It is also the one that requires testing, because real sites load things from more places than anyone remembers. Deploy it in report-only mode first:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

Nothing is blocked; violations are reported. Run it for a week, look at what would have broken, adjust, then switch to enforcing.

Note that 'unsafe-inline' for scripts largely defeats the purpose - it permits exactly the injected inline script CSP exists to stop. If your site relies on inline scripts, moving them to files is the work that makes CSP meaningful. Being honest about it: a policy with script-src 'unsafe-inline' will pass most automated checks while providing very little of the protection the header is for.

X-Frame-Options

X-Frame-Options: SAMEORIGIN

Stops your pages being embedded in a frame on someone else's site, which is the mechanism behind clickjacking - a transparent frame over a page the victim believes they are interacting with.

Modern equivalent is CSP's frame-ancestors, which is more expressive. Send both: frame-ancestors for browsers that honour it, X-Frame-Options for anything older.

X-Content-Type-Options

X-Content-Type-Options: nosniff

One value, no options, no downside. Stops the browser second-guessing your declared content type. Without it, a file you serve as text can be interpreted as JavaScript if the browser thinks it looks like JavaScript - which matters most for user-uploaded content.

If you add exactly one header today, this is the one with the best ratio of protection to risk.

Referrer-Policy

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

Controls how much URL information is sent when a visitor follows a link off your site. The default behaviour leaks full URLs to third parties, which matters if your paths contain anything sensitive - a reset token, an internal identifier, a customer reference.

The value above sends the full URL within your own site, and only the origin when leaving it. Sensible for nearly everyone.

Permissions-Policy

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

Declares which browser features your site may use. Empty parentheses mean "not at all, including in embedded frames". If your site does not need the camera, saying so removes a capability that any injected or embedded content could otherwise attempt to reach.

The six at a glance

HeaderStopsRisk of adding
Strict-Transport-SecurityDowngrade to plain HTTPMedium. Hard to retract
Content-Security-PolicyInjected script executionHigh. Test first
X-Frame-OptionsClickjacking via framingLow
X-Content-Type-OptionsMIME type guessingNone
Referrer-PolicyURL leakage to third partiesLow
Permissions-PolicyUnwanted use of camera, mic, locationLow

Adding them, in order

On Apache or LiteSpeed - which includes most shared hosting - all six go in .htaccess:

<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header always set X-Content-Type-Options "nosniff"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
</IfModule>

A sensible sequence:

  1. Add the four safe ones first - nosniff, X-Frame-Options, Referrer-Policy, Permissions-Policy. These essentially never break anything.
  2. Add HSTS once you have confirmed every hostname serves HTTPS properly.
  3. Work on CSP separately, in report-only mode, over a week or two. It deserves its own change rather than being bundled with the others.

Then verify from outside, because a header set in config is not the same as a header actually sent - a CDN or proxy in front of your origin can strip or override them:

curl -sI https://yourdomain.com | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy'
What a scan can and can't confirm here. Headers are the most reliably checkable item on an external report - they are either in the response or they are not, and a scanner reads exactly what a visitor's browser receives. The limitation is depth rather than accuracy: a check can confirm a Content-Security-Policy header exists, but assessing whether that policy is actually restrictive is a different job. A CSP permitting 'unsafe-inline' scripts passes the presence test while doing little. Present is not the same as effective.

Headers you can safely ignore

Several headers still appear in older hardening guides and in some scanner output. Adding them is not harmful, but it is not useful either, and knowing which are obsolete saves arguing about them.

  • X-XSS-Protection controlled a browser XSS filter that has been removed from modern browsers. In some older versions the filter introduced its own vulnerabilities, which is why it was dropped. Content-Security-Policy replaced it.
  • Expect-CT is obsolete. Certificate Transparency enforcement is now built into browsers directly, so the header does nothing.
  • Feature-Policy is the previous name for Permissions-Policy. Use the current one; there is no reason to send both unless you need to support quite old browsers.
  • Public-Key-Pins was removed from browsers, and was dangerous while it lasted: a pinning mistake could make a site unreachable for the duration of the policy.

If a tool flags any of these as missing, that is a signal about the tool rather than your site.

When a CDN is in the way

If something sits between your origin and your visitors - a CDN, a reverse proxy, a platform's edge - then the headers you configure and the headers your visitors receive are two different things.

Three failure modes, all of which produce a report that disagrees with your config file:

  • The edge strips them. Some configurations pass through only a known set of headers.
  • The edge adds its own. You may end up with two X-Frame-Options headers with different values, and browser behaviour when headers conflict is not something to rely on.
  • The edge serves cached responses from before your change, so the new headers appear only after the cache expires.

The resolution is the same in each case: verify from outside, against the public hostname, not against the origin. A header that exists in .htaccess but never reaches a browser is not protecting anyone, and this is the single most common reason a headers check fails on a site where someone has genuinely configured them.

What they are and are not

Headers are a browser-side layer. They reduce the impact of certain client-side attacks; they do nothing about an insecure API, a weak password policy or an exposed credential.

What makes them worth doing is the ratio. Five of the six are one line each, they break nothing, and they are visible to anyone assessing you from outside - which is why they appear on every external report, including in your external security posture. The sixth, CSP, is a genuine piece of work, and the only one on this list where the header being present tells you considerably less than you would hope.

Questions people ask about this

Which security headers should every site have?

Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, Referrer-Policy and Permissions-Policy are safe to add to essentially any site and rarely break anything. Content-Security-Policy is the most valuable of the six but needs testing, because a strict policy can block your own scripts and styles.

Can adding security headers break my website?

Content-Security-Policy can, by blocking resources you actually use - inline scripts, third-party widgets, fonts from another origin. The others are low risk. Roll CSP out in report-only mode first so violations are reported without anything being blocked.

Is HSTS safe to enable?

Yes, provided your site fully works over HTTPS first. HSTS instructs browsers to refuse plain HTTP for your domain for the duration of max-age, which cannot be undone quickly. Take particular care with includeSubDomains: every subdomain must serve HTTPS before you publish it.

Do security headers help with compliance?

They are evidence of one control among many, not compliance in themselves. Frameworks like SOC 2 look at process - how changes are reviewed, how access is governed, how incidents are handled. Headers are a visible artefact of good practice, not a substitute for the practice.

See which headers your site sends

A passive check reads your response headers and reports which of the six are present.

Run a free check