Skip to content
🚀 10 tools live, free forever — the toolbox keeps growing
Home›Blog›HTTP Security Headers: A Complete Guide to CSP, HSTS and the Rest
Guides

HTTP Security Headers: A Complete Guide to CSP, HSTS and the Rest

A handful of response headers decide whether a browser will run injected scripts, frame your pages, or fall back to plain HTTP. This guide explains what each one does, which ones are worth the effort, and how to deploy a strict CSP without breaking anything.

✍ By the Founder & CEO, Team BuildToolForge🗓 2026-10-12⏱ 12 min
securitycsphstsheadershttp
Web Security Guide2026

Security Headers
Made Practical

CSP, HSTS and the four smaller headers, explained line by line

Copy-ready policies · Rollout plan · No signup

curl -I · example.com
$ curl -I https://example.com
CSPdefault-src 'self'nonce
HSTSmax-age=31536000
XCTOnosniff
Refstrict-origin-when-cross
✓ 6 of 6 headers present · no unsafe-inline
The headers
CSP
What may load
HSTS
HTTPS only
XCTO
No MIME sniffing
Referrer
What leaks out
Permissions
Device access
COOP
Window isolation
Blocks injected scriptsStops protocol downgradePrevents clickjackingLimits data leakageFree to check

Most of a website's security lives in its code, but a small set of HTTP response headers decides what the browser is willing to do with that code once it arrives. They are the difference between an injected <script> that executes and one the browser refuses to run, between a first visit over plain HTTP and one that is upgraded before it leaves the address bar.

They are also unusually cheap to deploy. Headers are configuration, not code: most are a single line in your web server, CDN or framework, and they apply to every response at once. This guide covers what each header does, which ones earn their keep, how to roll out a Content-Security-Policy without breaking your own site, and how to check what you are sending today.

Why Security Headers Matter

A header is an instruction to the browser, and the browser is the only party that can enforce it on the user's machine. That makes headers a second line of defence that holds even when something upstream has already gone wrong:

  • They contain mistakes you have already made. A Content-Security-Policy does not fix an XSS hole, but it can stop the injected script from running or from sending anything back.
  • They close whole classes of attack. Clickjacking, MIME confusion and protocol downgrade are not bugs in your code; they are browser behaviours you have to opt out of.
  • They stop quiet data leakage. Without a referrer policy, the full URL of a private page travels to every third party you load an image from.
  • They are the first thing an auditor looks at. Missing headers show up in every automated scan, security questionnaire and vendor review, because they are the cheapest possible finding.

The catch is that they are easy to get almost right. A CSP with unsafe-inline blocks very little; an HSTS header with a one-day lifetime protects almost nobody. The details are where the value is.

The Headers That Matter Most

Six headers do nearly all of the useful work. The rest are either legacy, narrowly scoped, or superseded.

HeaderWhat it preventsEffort
Content-Security-PolicyXSS, data exfiltration, unexpected third-party codeHigh
Strict-Transport-SecurityDowngrade to HTTP, cookie theft on first requestLow
X-Content-Type-OptionsMIME sniffing an upload into a scriptTrivial
Referrer-PolicyLeaking full URLs to third partiesTrivial
Permissions-PolicyCamera, microphone and geolocation accessLow
Cross-Origin-Opener-PolicyCross-window attacks from pages you openLow

Start at the bottom. The four cheap headers are one line each and almost never break anything, so they are worth shipping today; CSP is the one that needs a plan.

Content-Security-Policy

CSP is an allowlist telling the browser where each kind of resource may come from. A reasonably strict policy looks like this:

default-src 'self'; script-src 'self' 'nonce-r4nd0m'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none'

Read it directive by directive. default-src 'self' is the fallback: anything not named explicitly may only load from your own origin. script-src is the one that matters most, because scripts are what an attacker wants to run. frame-ancestors 'none' says no other site may put your page in an iframe, which is the modern replacement for X-Frame-Options. object-src 'none' and base-uri 'self' close two old bypasses that cost nothing to shut.

Two values decide whether your policy is real. 'unsafe-inline' in script-src permits any inline <script>, including one an attacker injected, which is precisely the thing CSP exists to stop. 'unsafe-eval' permits eval() and its relatives. A policy with either is worth having for the non-script directives, but it is not protecting you from XSS.

The way out is a nonce or a hash. A nonce is a random value generated per response and attached both to the header and to each legitimate <script nonce="...">; the browser runs only scripts carrying that value, and an injected script cannot guess it. Hashes work the same way for scripts that never change. If your framework renders inline scripts, it almost certainly has nonce support already; turning it on is usually a config flag.

Strict-Transport-Security

HSTS tells the browser to refuse plain HTTP for your domain for a given period, which closes the gap that a redirect alone leaves open: the very first request, before the redirect arrives, travels unencrypted and can be intercepted.

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

The max-age is in seconds; one year is the usual target, and anything under a few months offers little. includeSubDomains extends the rule to every subdomain, which is what makes it effective and also what makes it worth checking first: any subdomain still served over HTTP will break. preload asks to be included in the list browsers ship with, so even the first visit is protected.

Treat HSTS as hard to reverse. A browser that has seen a one-year policy will enforce it for a year, whatever you send afterwards. Roll it out with a short max-age, confirm nothing broke, then raise it. And because HSTS only matters if the certificate behind it is valid, confirm the certificate and its expiry with the SSL Certificate Checker before you commit to a long policy.

The Smaller Headers

These four are one line each and rarely controversial:

  • X-Content-Type-Options: nosniff — stops the browser guessing a response's type from its content. Without it, a file uploaded as a .txt can be executed as script if the browser decides it looks like one.
  • Referrer-Policy: strict-origin-when-cross-origin — sends the full URL to your own origin, only the origin to other HTTPS sites, and nothing when downgrading to HTTP. This is the modern default and stops private path and query data leaking to third parties.
  • Permissions-Policy: camera=(), microphone=(), geolocation=() — switches off device features your site does not use, for your own page and for anything you embed.
  • Cross-Origin-Opener-Policy: same-origin — severs the window relationship with pages you open or that open you, which blocks a family of cross-window attacks and is a prerequisite for some browser isolation features.

X-Frame-Options: DENY still belongs on the list for older browsers, but frame-ancestors in your CSP supersedes it and is more expressive. Send both; they do not conflict.

Rolling Out a CSP Safely

A CSP written from guesswork will block something your own site needs. The report-first path avoids that entirely:

  1. Ship the four cheap headers today. nosniff, a referrer policy, a permissions policy and HSTS with a short max-age. None of them normally breaks a working site.
  2. Add Content-Security-Policy-Report-Only. Identical syntax, but the browser only reports violations instead of blocking them. Your site behaves exactly as before.
  3. Collect reports for a week or two. They will name every third-party script, font, analytics endpoint and inline handler you forgot about. This is the step people skip, and it is the one that makes the rest painless.
  4. Tighten until the reports are quiet. Add the sources you actually need, and remove the ones you discover you do not. Resist widening a directive to * to silence a single report.
  5. Switch to enforcing. Rename the header to Content-Security-Policy. Keep the report-only version alongside it when you later want to test a stricter policy.
  6. Remove unsafe-inline last. This is the real work: move inline scripts into files or give them nonces. Everything before this step is preparation; this is the step that stops XSS.

One practical note: headers are per-response, so set them at the edge — your CDN, reverse proxy or framework middleware — rather than page by page. Setting them in one place is also the only way to be sure your error pages and redirects carry them too, which is exactly where they are usually missing.

Headers You Can Stop Sending

Some advice has aged badly, and sending a dead header is not harmless: it adds bytes and makes an audit look done when it is not.

  • X-XSS-Protection — the browser filters it controlled are gone. It does nothing in any current browser, and the old mode=block value once introduced its own problems. Drop it and rely on CSP.
  • Public-Key-Pins — HPKP is removed from browsers. It was easy to lock yourself out of your own domain, which is why it was abandoned.
  • Expect-CT — certificate transparency is now enforced by default, so the header is redundant.
  • Feature-Policy — renamed to Permissions-Policy, with different syntax. Send the new one; keep the old only if you must support something ancient.

Conversely, one thing worth removing is information you volunteer: a Server or X-Powered-By header naming your exact framework version tells an attacker which exploits to try first. It is not a vulnerability, but there is no reason to publish it.

How to Check Your Own Headers

Headers are public, so checking any site's configuration takes seconds:

  1. curl -I https://example.com. A HEAD request prints every response header without downloading the page. Add -L so you inspect the final URL rather than a redirect.
  2. The browser's network panel. Open developer tools, reload, select the top-level document and read the Response Headers section. The console also logs each CSP violation with the directive that blocked it, which is the fastest way to debug a policy.
  3. A checker that grades the result. Our Security Headers Checker fetches a URL and reports which headers are present, which are missing, and where a present header is weaker than it looks.

Check more than your homepage. Headers are often set on the main application but not on static asset hosts, error pages, API subdomains or the redirect that sends www to the apex. While you are there, confirm that the redirect chain itself is correct; the HTTP Status Codes reference covers which code belongs on a permanent move.

To write a policy without memorising the syntax, start from our CSP generator, which also flags the settings that weaken it.

Common Mistakes to Avoid

  • A CSP with unsafe-inline in script-src. It passes most automated checks and stops almost no XSS. Use a nonce or hashes.
  • Widening a directive to * to fix one report. Add the specific origin instead; * gives up the directive entirely.
  • HSTS with a long max-age on day one. It is effectively irreversible for the lifetime you published. Start short.
  • includeSubDomains before auditing subdomains. Anything still on HTTP, including internal tools, stops loading.
  • Headers on the app but not at the edge. Error pages, redirects and asset hosts end up unprotected, and those are the responses attackers reach for.
  • Setting the same header twice. Two CSP headers are intersected, not merged, so the result is stricter than either and usually breaks something; two conflicting X-Frame-Options values may be ignored entirely.
  • Still shipping X-XSS-Protection and calling it done. A dead header gives false comfort in a report.
  • Never moving past report-only. Reports you do not act on protect nobody.

Check Your Own Headers

Security headers are the highest ratio of protection to effort available on the web: four of them are a single line each, and the fifth, CSP, is mostly a matter of listening to your own violation reports before you enforce anything. Start by looking at what your site sends today, add the cheap headers immediately, then put a report-only CSP in place and let it tell you what your policy needs to say.

Related tools and guides:

Frequently Asked Questions

Which security headers should I add first?⌄

Start with the four that almost never break anything: X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, a Permissions-Policy switching off device features you do not use, and Strict-Transport-Security with a short max-age you raise later. Then add a Content-Security-Policy in report-only mode, because that is the one that needs observation before enforcement.

Does a CSP with unsafe-inline still help?⌄

It helps with everything except the attack CSP is best at stopping. Directives like img-src, connect-src, frame-ancestors and object-src still constrain where data can go and who can frame you. But 'unsafe-inline' in script-src permits any injected inline script to run, so the policy is not protecting you from XSS. Replace it with a per-response nonce or script hashes.

Is Strict-Transport-Security risky?⌄

It is risky mainly because it is hard to undo: a browser that saw max-age=31536000 will refuse plain HTTP for your domain for a year regardless of what you send next. Deploy it with a short max-age first, confirm every subdomain is reachable over HTTPS before adding includeSubDomains, and only then raise the lifetime and consider preload.

What replaced X-Frame-Options and X-XSS-Protection?⌄

The frame-ancestors directive in Content-Security-Policy replaces X-Frame-Options and is more expressive, though sending both is harmless and still helps older browsers. X-XSS-Protection has no replacement because the browser filters it controlled were removed; CSP is what stops XSS now, and the old header can simply be dropped.

Where should I set security headers?⌄

At the edge, in one place: your CDN, reverse proxy or framework middleware. Setting them per page leaves your error pages, redirects, asset hosts and API responses uncovered, and those are exactly the responses that get missed in an audit and reached for in an attack.

How do I check which headers my site sends?⌄

Run curl -I https://yourdomain.com, adding -L so you see the final destination rather than a redirect, or open the browser's network panel and read the Response Headers for the top-level document. The Security Headers Checker does it from your browser and flags both missing headers and present ones that are weaker than they look.

About the author
Founder & CEO, BuildToolForge
Follow on X: @Buildtoolforge

15+ years of hands-on experience in web development and security. Focused on practical web security: keeping websites secure, reliable and easy to troubleshoot. BuildToolForge exists to make these checks free, private and understandable. Tools run in your browser wherever possible, and nothing you enter into a tool is stored.

How we write our guides: Every guide is written from hands-on experience, checked against the official standards, and tested with our own tools on real domains. We update guides when standards or tools change. Found a mistake? Email contact@buildtoolforge.com.

Last updated: November 2, 2026