Duvaxa

10 min read · Updated 26 Oct 2026

Security headers explained: what each one does and which you need

In short: Seven HTTP response headers block whole classes of attack at almost no cost. Here is what each one does, the value to start with, and the mistakes that break sites.

Free tool: Security headers check →

When a browser loads a page, the server's response carries headers as well as HTML. Most headers deal with caching and content types, but a small group tells the browser how to protect the person using the page. They don't change how the site looks, they cost nothing to serve, and they're among the first things security questionnaires, penetration testers and government audits check. If your site has none of them, adding them is one of the cheapest security improvements you can make this week.

Strict-Transport-Security (HSTS)

HSTS tells the browser: for the next N seconds, only ever talk to this site over HTTPS. Without it, the very first request someone makes by typing your domain goes out over plain HTTP before your redirect kicks in, and anyone on the same Wi-Fi or network path can intercept or alter it. With HSTS, the browser upgrades every request itself.

Strict-Transport-Security: max-age=31536000; includeSubDomains
  • max-age is in seconds. 31536000 is one year; six months (15552000) is a common minimum.
  • includeSubDomains extends the rule to every subdomain. Only add it once every subdomain works on HTTPS.
  • preload asks browsers to ship your domain in their built-in HSTS list. It's hard to undo, so add it last, if at all.

Content-Security-Policy (CSP)

CSP lists the places a page may load scripts, styles, images, fonts and frames from. If an attacker manages to inject a script tag through a comment field or a compromised plugin, the browser refuses to run it because its source isn't on the list. It is the most powerful of the security headers, and the one that needs the most care.

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'

Two values undo most of CSP's protection: 'unsafe-inline' (allow any inline script) and 'unsafe-eval' (allow strings to be run as code). Wildcards and scheme-only sources such as https: in script-src do the same. If your site relies on inline scripts, move them into files, or allow them one by one with a hash or a nonce rather than allowing all inline code.

X-Content-Type-Options

Browsers used to guess a file's type from its contents, so a text file uploaded by a user could be run as a script. This header turns the guessing off. There is one correct value and no downside.

X-Content-Type-Options: nosniff

Clickjacking protection: frame-ancestors and X-Frame-Options

Clickjacking loads your page inside an invisible frame on another site and tricks visitors into clicking buttons they can't see, such as "confirm transfer" or "delete account". The modern control is the CSP frame-ancestors directive; the older X-Frame-Options header does the same for older browsers. Use frame-ancestors 'none' if nobody should frame your pages, or 'self' if only your own site does.

Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN

Referrer-Policy

When someone clicks a link from your site to another, the browser can tell the other site where they came from. Full page addresses often include search terms, account IDs or password-reset tokens. strict-origin-when-cross-origin sends only your domain to other sites, and the full address within your own site. Modern browsers use it by default, but setting it explicitly keeps older browsers and embedded webviews in line. Avoid unsafe-url and no-referrer-when-downgrade, which send full addresses.

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

Permissions-Policy

Permissions-Policy switches off browser features your site never uses: camera, microphone, location, payment requests and others. If a third-party script or an injected frame tries to use them, the browser refuses. List only features you don't need with an empty allow-list.

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

Cookie flags

Cookies aren't a header you add once, but the Set-Cookie header has three flags that matter. Secure stops the cookie being sent over plain HTTP. HttpOnly stops JavaScript reading it, so a cross-site scripting bug can't steal a session. SameSite (Lax or Strict) stops it being sent with requests started by other sites, which blocks most cross-site request forgery. Session cookies should have all three.

Headers you can remove

Server and X-Powered-By headers that announce exact versions (Apache/2.4.41, PHP/7.4.3) help attackers pick exploits that match your stack. They do nothing for visitors. Turn off version tokens in the web server and remove X-Powered-By in the framework.

A safe order to roll them out

  1. Add X-Content-Type-Options, Referrer-Policy and Permissions-Policy. These rarely break anything.
  2. Add frame protection, after checking whether any partner legitimately embeds your pages.
  3. Add HSTS with a short max-age such as one day, confirm everything works on HTTPS, then raise it to six months or a year.
  4. Add CSP in Content-Security-Policy-Report-Only mode, collect reports for a week or two, fix the violations, then switch to enforcing.
  5. Remove version disclosure headers and set the cookie flags in your application.

Where to set them

Set headers once, as close to the edge as you can: in your CDN or reverse proxy (Cloudflare, nginx, Apache), or in your hosting platform's configuration (Vercel's vercel.json, Netlify's _headers file). Setting them in application code works too, but it's easy to miss error pages, static files and redirects. Our guide to HSTS and CSP on nginx and Cloudflare has copy-paste configuration.

A worked example: a typical small business site

Take a company website on WordPress behind Cloudflare, with Google Analytics, a chat widget and a contact form. Out of the box it usually sends none of these headers, and grades F. A sensible target looks like this: HSTS for a year with includeSubDomains, set in Cloudflare; X-Content-Type-Options, Referrer-Policy and Permissions-Policy set as static headers in a Cloudflare Transform Rule; frame-ancestors 'self' so only the site itself can frame its pages; and a Content-Security-Policy that lists the site's own domain, the analytics host and the chat widget's hosts.

The CSP is the part that takes a week rather than an hour, because WordPress themes and plugins often add inline scripts. Run the policy in report-only mode, list the violations, and replace or remove the plugins responsible. Many sites end up allowing 'unsafe-inline' for styles but not for scripts, which keeps most of the protection.

Common mistakes

  • Setting headers in the application but not on static files, error pages or redirects, which are served by a different layer.
  • Sending the same header twice with different values, once from the CDN and once from the server. Browsers may combine or ignore them.
  • Adding HSTS with preload and includeSubDomains before checking that every subdomain, including old marketing pages and mail services, works on HTTPS.
  • Copying a CSP from another site. Policies are specific to the scripts each site loads.
  • Adding X-Frame-Options: DENY and breaking a partner or internal tool that legitimately embeds a page.
  • Setting Permissions-Policy with the older Feature-Policy syntax, which modern browsers ignore.

How to verify

Check the raw response with curl -sI https://example.com, or in the browser's developer tools under Network, selecting the document request. Confirm each header appears once with the expected value, then check a missing page (to see the 404 response) and a static file such as an image. Finally, run an external check from outside your network, because some headers are added by the CDN and won't appear if you test the origin server directly.

Checklist

  • HTTP redirects to HTTPS everywhere.
  • Strict-Transport-Security with at least six months and includeSubDomains.
  • A Content-Security-Policy without 'unsafe-inline' or 'unsafe-eval' for scripts.
  • X-Content-Type-Options: nosniff.
  • frame-ancestors or X-Frame-Options set.
  • Referrer-Policy set to strict-origin-when-cross-origin or stricter.
  • Permissions-Policy turning off unused features.
  • Session cookies with Secure, HttpOnly and SameSite.
  • No version numbers in Server or X-Powered-By.

FAQ

Questions

Do security headers affect SEO?

Not directly. HTTPS is a ranking signal, and HSTS supports it, but the other headers don't change rankings. They do affect the trust signals buyers and auditors look for.

Is X-XSS-Protection still needed?

No. Modern browsers have removed the filter it controlled, and a strong CSP replaces it. You can leave it out.

Can headers break my website?

CSP and HSTS can if added carelessly. Use CSP's report-only mode first, and start HSTS with a short max-age.

Should every page have the same headers?

Usually yes. Set them site-wide at the server or CDN so error pages, static files and redirects are covered too.

How do I check my site?

Run our free security headers check. It grades the headers from A+ to F and gives the fix for nginx, Apache, Cloudflare, Vercel and Netlify.

Tools for this

Products that help