Skip to content
Cyber Security7 June 2026 · By the Intense Path Editorial Team

Security Headers: Which Ones Matter and What Each One Stops

Most of the value sits in four headers, not fourteen. Here is what each one actually blunts, and why a badly written policy can break your site while doing nothing at all for its safety.

On This Page
Pass It On

Found this useful? Send it to someone who’s building.

HTTP Security Headers: What Each One Stops | Intense Path

A scanner produced a report, the report listed nine HTTP security headers you do not send, and somebody has asked whether that is bad. The honest answer: four of the nine carry almost all the value, two are obsolete and should never have been on the list, and one of the four can take your domain off the internet for a year if you set it carelessly. Working out which is which is the entire job.

A security header is a sentence the server says to the browser about how to treat this particular response. That is all it is. It does not patch a bug, sanitise an input, or stop anyone reaching your server. What a good header does is narrow what a successful attack is able to do next, which is worth a great deal when the attack is injected script and worth nothing whatsoever when the attack is a stolen administrator password.

So treat them as mitigation, not repair. They are also unusually cheap: most are one line in a server configuration, a proxy rule or an edge function, and none require touching application code. Cheap and genuinely useful is a rare pairing, which is why the short list below is worth working through properly instead of pasting from somebody else’s configuration. It belongs in ongoing site maintenance rather than in a one-off hardening sprint that nobody revisits.

What a header can do, and what it cannot

Every one of these instructions is enforced by the browser, not by you. That has three consequences people forget. A visitor using something that ignores the header gets no protection. A response that does not carry the header gets no protection, which includes error pages, redirects, uploaded files and API responses. And the instruction applies to an origin, so a policy on the main site does nothing for the subdomain running the old application.

There is also a layering problem that catches teams out constantly. Headers can be set at the origin, at a reverse proxy, at the CDN edge, and by the framework. When two of those set the same header the result is either a duplicate or a silent override, and which one wins depends on the stack. Decide on one place where security headers are written, put a comment there saying so, and check the live response rather than the configuration file when you want to know what is actually being sent.

Check the response, not the config

Ask for a real page, an image, a JSON endpoint and a deliberately broken URL, and read the headers on all four. Teams routinely discover that the policy applies to HTML pages served by the framework and to nothing else, so the file upload directory and the 404 page, which are the two places an attacker is most interested in, are the two places with no policy at all.

The short list, and what each one blunts

This is the set worth arguing about. Everything outside it is either obsolete, situational, or already covered by something here.

HeaderWhat it bluntsWhat it costs when set wrongly
Content-Security-PolicyInjected script, framing by another site, data sent to unknown hostsFeatures break silently, then somebody switches the whole policy off
Strict-Transport-SecurityDowngrade to plain HTTP and interception of the first requestA long lifetime on a domain that later cannot serve HTTPS is not reversible in time
X-Content-Type-OptionsA response being executed as a type it never claimed to beAlmost nothing, beyond rare breakage in badly typed legacy assets
Referrer-PolicyFull URLs, including tokens in query strings, leaking to third partiesAttribution reporting loses some source detail
Permissions-PolicyThe page and its embedded frames reaching camera, microphone or locationA legitimate embed quietly loses a capability it needed
Cross-Origin-Opener-PolicyA window you opened keeping a handle back into yoursPopup-based sign-in and payment flows stop completing
Set-Cookie flagsSession theft by script, and requests forged from another siteA strict same-site rule signs people out of journeys that begin elsewhere

Two headers that still appear on scanner reports should be treated as noise. X-XSS-Protection controlled a filter that modern browsers removed, and the safest value is zero rather than one. X-Frame-Options is not wrong, but frame-ancestors inside a content security policy supersedes it and expresses more; send the old one only while you still support something that needs it.

Content Security Policy: the one worth the effort

A content security policy is a list of the sources a page is allowed to load and execute things from. Script from these origins, styles from these, frames from these, connections to these, and this page may be framed by nobody. Anything outside the list is refused by the browser before it runs.

What a policy actually stops

The value is not in preventing injection. It is in making injection useless. An attacker who gets a script tag into a comment field on a page with a strict policy has planted something the browser will refuse to execute, because it carries no valid nonce and its source is not on the list. The same policy stops the injected code from posting what it collected to an address you have never heard of, because that address is not in connect-src. Two of the most damaging outcomes are removed while the underlying bug is still there.

The frame-ancestors directive is the other half, and it is the cleanest fix for clickjacking there is: an attacker cannot layer your page invisibly under theirs if the browser refuses to render it inside their frame at all. On a site where the same request also lands on legacy plugin output, this is one of the settings that quietly does more than the plugin scanner does. Hardening WordPress has the same shape: a small number of settings that remove whole categories of outcome.

Why most policies fail

Almost every weak policy in the wild failed the same way. Somebody enforced a strict policy, the tag manager stopped working, marketing complained, and the fix was to add unsafe-inline to script-src. That single addition undoes most of what the policy was for, because injected inline script is exactly what unsafe-inline permits. The policy is still present. It still scores well on a checker that only looks for the header. It no longer stops the attack it was written for.

The alternative is a nonce or a hash on every inline script you genuinely need, generated per response, which means the policy has to be produced by something that renders the page rather than pasted into a static configuration. That is real work, and it is the reason a serious policy belongs in the application build rather than bolted on afterwards. If your marketing stack lets any team member inject arbitrary script through a tag manager, be honest about it: you cannot have both that and a strict policy, and pretending otherwise produces the worst of the two.

A policy you weakened until it stopped complaining is a policy that stopped protecting. It is now decoration with a maintenance cost.

Transport, cookies, and the one-way door

Strict-Transport-Security tells the browser to refuse plain HTTP for this host until the stated lifetime expires. It closes the gap where a first request travels unencrypted and can be redirected somewhere else, and it removes the click-through warning that people learn to ignore. Setting it is a single line. Understanding it is slightly more than that.

The preload decision is separate

The lifetime is the risk. A browser that has seen a long lifetime will keep refusing plain HTTP for that period even if you change your mind, and it will do so for every subdomain if you included them. Submitting the domain to the browser preload list goes further: the rule ships inside the browser, applies before any visit, and removal takes as long as browsers take to release. Start with a short lifetime, watch for anything on a subdomain that still cannot serve HTTPS, then raise it. Treat preload as its own decision with its own sign-off.

Cookies belong in the same conversation, because a session cookie is what most of this is protecting. Three flags carry it: Secure so it never travels unencrypted, HttpOnly so script cannot read it, and a same-site rule so another site cannot ride on it. Strict is safest and will sign people out of journeys that begin on someone else’s page, so Lax is usually the working answer. Add no-store caching on authenticated responses while you are there, because a shared cache holding one person’s account page is a breach that no header on the list above prevents.

The cheap ones you should already be sending

These take an afternoon between them, carry very little breakage risk, and remove real categories of problem. There is no good argument for not having them.

  • X-Content-Type-Options: nosniff. Stops the browser guessing that a file is script when the server said it was something else. This is what turns a permissive upload directory from an inconvenience into remote code in someone’s browser.
  • Referrer-Policy. Controls how much of the current URL travels to the next site. Send the origin rather than the full path, so password-reset links, search terms and identifiers in query strings stop being handed to every embedded third party.
  • frame-ancestors in your policy. Names who may frame you, which for most marketing sites is nobody. Cheaper and clearer than the header it replaced.
  • Permissions-Policy. Switches off capabilities the page never uses, so an embed cannot ask for the camera or location on your domain’s behalf. Write it as a deny list of everything, then re-enable what you actually need.

The referrer setting is the one most people underrate, because it is really a privacy control wearing a security label. Every full URL you emit is data you have handed to somebody else’s logs, permanently, without a contract. That is the same argument as collecting less in the first place: the safest data is the data you never sent.

When a wrong policy is worse than no policy

Here is the concession, and it is not a small one. A misconfigured header is frequently worse than an absent one, in two distinct ways.

The first is breakage. A strict policy shipped without a rehearsal breaks the payment widget, the embedded map, the consent banner and the analytics, usually on a Friday, usually to the accompaniment of somebody suggesting the whole idea was overcautious. What follows is a rushed weakening, and the weakened version is what stays in production for the next three years.

The second is worse and quieter: a header present but hollow produces a green tick on a report, and a green tick ends the conversation. Anyone reading the report afterwards believes the class of attack is handled. This is why a finding needs to say what was tested and what the result actually means rather than which string was present, a point our parent company makes directly in the anatomy of a finding. Tooling that resolves raw checks into findings with severity, priority and a recommendation, as Prooflin does, is more useful here than a checker that awards a grade for the presence of a string.

The report-only phase is not optional

Content-Security-Policy-Report-Only sends the same policy, enforces nothing, and reports every violation it would have caused. Run it long enough to cover a full publishing cycle, including a campaign launch and whatever marketing adds mid-month. Then read the reports properly: some violations are things to allow, and some are things you did not know were on your site, which is worth the exercise on its own.

Rolling them out without breaking Friday

The order matters more than the values. This is the sequence we would run, and it starts with an inventory for the same reason technical discovery starts with one: you cannot write a policy for traffic you have not looked at.

  1. Inventory what the pages actually load. Every script origin, font host, frame, image domain and outbound connection, on the templates that matter. Most teams find at least one thing nobody can account for.
  2. Ship the low-risk headers first. Nosniff, referrer policy, permissions policy and frame-ancestors. Get them applying to every response, including assets and error pages, and confirm it on the live site.
  3. Run the content policy in report-only. With an endpoint that actually collects the reports and a person whose job it is to read them. A report nobody reads is a phase you skipped with extra steps.
  4. Enforce on one route, then widen. Pick a route with real traffic and few third parties. Live with it for a week before extending it to the templates that carry marketing tags.
  5. Set transport security short, then raise it. Confirm every subdomain can serve HTTPS before you include subdomains, and keep the preload submission as a separate, later decision.
  6. Add the check to release. A test that requests four kinds of response and asserts the headers is a few minutes of work. Without it, the next platform migration silently drops all of this and nobody notices for a year.

What headers will never cover

Set all of them perfectly and you have still done nothing about the three things most likely to hurt you: an unpatched dependency, an account that should have been closed months ago, and somebody who typed a password into a convincing page. Headers constrain the browser. They have no opinion at all about the credentials of people who left, and that is a larger risk for most small organisations than any injection bug.

The decision rule, then. If you have an afternoon, send the four cheap headers and fix your cookie flags, because the ratio of protection to effort will not be beaten anywhere else on your site. If you have a fortnight, do the content policy properly, with report-only first and nonces rather than unsafe-inline. If you have neither, send nothing and write down that you send nothing, because a policy you cannot maintain will be weakened by the first person it inconveniences.

And keep the headers in proportion to the rest of the plan. They shrink the blast radius of an incident; they do not tell you an incident happened, and they will not help you at three in the morning. That is what preparing for the day you are compromised is for. If you want a second pair of eyes on what your site currently sends and what it should, send us the domain and tell us what is allowed to break.

Take these with you
Security headers narrow what a successful attack can do next; they never repair the bug that let it happen, so they belong alongside patching rather than instead of it.
Four cheap headers plus correct cookie flags deliver most of the available protection for an afternoon of work and almost no breakage risk.
A content policy weakened with unsafe-inline still passes checkers while no longer stopping injected script, which is the most common way a policy becomes decoration.
Run the content policy in report-only across a full publishing cycle before enforcing it, and read the reports rather than collecting them.
Transport security lifetimes and preload submission are hard to reverse, so raise the lifetime gradually and sign off preload separately.

Common questions.

Which HTTP security headers should a small business site send?

Start with four: X-Content-Type-Options set to nosniff, a Referrer-Policy that sends the origin rather than the full URL, a Permissions-Policy that denies capabilities the site does not use, and frame-ancestors to control who may embed your pages. Add Secure, HttpOnly and SameSite flags on cookies. These carry very little breakage risk and remove whole categories of problem for an afternoon of configuration work.

What does Content-Security-Policy actually protect against?

It makes injected script useless rather than preventing the injection. The browser refuses to run code from sources the policy does not list, so a script planted through a vulnerable field never executes, and it cannot send what it collected to an unlisted address. The frame-ancestors directive additionally prevents clickjacking by refusing to render your page inside another site frame.

Is unsafe-inline in a content security policy really that bad?

Yes, because inline script is precisely what most injection attacks produce. Allowing unsafe-inline in script-src removes the main protection the policy exists to provide, while the header stays present and still scores well on automated checkers. The supported alternative is a per-response nonce or a hash for each inline script you genuinely need, which requires the policy to be generated when the page renders.

Can setting Strict-Transport-Security break my site?

It can, and the damage is hard to undo. Browsers remember the instruction for the lifetime you declared and will refuse plain HTTP until it expires, including on every subdomain if you included them. If a subdomain cannot serve HTTPS, it becomes unreachable. Begin with a short lifetime, verify every host, then raise it, and treat browser preload submission as a separate decision.

Do security headers replace a web application firewall or patching?

No. Headers are instructions to the browser about a response that has already been generated, so they constrain consequences rather than block requests. They do nothing about unpatched dependencies, stolen credentials, exposed administration panels or phishing. Treat them as one inexpensive layer that reduces the impact of a successful attack, sitting alongside updates, access control and monitoring rather than substituting for any of them.

How do I check which headers my site is actually sending?

Request several different kinds of response and read the headers on each: an ordinary page, a static asset, a JSON endpoint and a URL that does not exist. Configuration files are unreliable evidence because a proxy, CDN or framework may add, duplicate or override them. Many sites find headers apply only to pages rendered by the application, leaving uploads and error pages unprotected.

Facing this in your
own business?

Tell us where you’re headed — we’ll map the shortest honest route.

Start a Project