Skip to content
pwnsy
web-securityintermediate#web-security#http-headers#csp#hardening#owasp

HTTP Security Headers: What Each Does & the Value to Set

A defensive reference to HTTP security response headers: what each one does, a recommended value, and the attack it mitigates, from CSP to Cache-Control.

A web server can tell the browser how to behave. HTTP security headers are the response headers that carry those instructions: load scripts only from here, never speak plain HTTP again, do not let another site frame this page. The browser enforces them, which makes headers a cheap and durable layer of defense that sits in front of your application logic.

This is a reference for the headers worth setting and the value to start from. Treat the recommended values as safe defaults to test, since a policy that is too strict can break a working site and one that is too loose does nothing. The point is to know what each header buys you and where it fits.

The core security response headers

Header names are exact and case-insensitive on the wire. The recommended value is a sound starting point; tune it to your application.

HeaderWhat it doesRecommended valueAttack it mitigates
Content-Security-PolicyControls which origins the browser may load script, style, images, frames, and other content fromdefault-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'Cross-site scripting (XSS), data injection, clickjacking
Strict-Transport-SecurityForces HTTPS for the whole domain for a set durationmax-age=31536000; includeSubDomainsSSL stripping, protocol downgrade, cookie theft on http
X-Content-Type-OptionsTells the browser not to guess (sniff) a response's content typenosniffMIME confusion attacks, drive-by execution of uploaded files
X-Frame-OptionsLegacy control on whether the page may be framedDENYClickjacking (superseded by CSP frame-ancestors)
Referrer-PolicyControls how much of the URL is sent in the Referer headerstrict-origin-when-cross-originLeakage of sensitive paths and tokens to third parties
Permissions-PolicyAllows or denies browser features (camera, geolocation, microphone) per origingeolocation=(), camera=(), microphone=()Abuse of powerful device APIs by injected or third-party code
Cross-Origin-Opener-PolicyIsolates your browsing context from cross-origin windowssame-originCross-window attacks, Spectre-style side channels
Cross-Origin-Embedder-PolicyRequires cross-origin resources to explicitly opt inrequire-corpCross-origin data leaks, enables cross-origin isolation
Cross-Origin-Resource-PolicyLimits which origins may embed a given resourcesame-originCross-origin resource theft and side-channel reads
Cache-ControlControls whether and where a response may be cachedno-store (on sensitive pages)Sensitive data left in browser or shared caches

The first four rows carry most of the value for most sites. Content-Security-Policy and a framing control together blunt the two most common client-side attacks, HSTS locks the transport, and nosniff removes a whole class of content-type tricks.

Content-Security-Policy in more depth

CSP is the header with the most leverage and the most ways to get wrong. It works by listing allowed sources per content type through directives.

DirectivePurposeExample
default-srcFallback source list for content types not named explicitlydefault-src 'self'
script-srcWhere scripts may load fromscript-src 'self'
style-srcWhere stylesheets may load fromstyle-src 'self'
img-srcWhere images may load fromimg-src 'self' data:
connect-srcEndpoints for fetch, XHR, and WebSocketconnect-src 'self'
object-srcSources for plugins, usually disabledobject-src 'none'
frame-ancestorsWho may frame this page, the modern clickjacking controlframe-ancestors 'none'
base-uriRestricts the base tag to stop base-tag injectionbase-uri 'self'

The strongest CSPs avoid 'unsafe-inline' and 'unsafe-eval' for scripts, using nonces or hashes instead so that only your own vetted scripts run. That is what turns CSP from a checkbox into a real barrier against injected script. Roll it out in report-only mode first, watch the violation reports, then enforce.

Report-only before enforce

Deploy a new Content-Security-Policy with the Content-Security-Policy-Report-Only header first. The browser reports what the policy would have blocked without breaking the page. Read those reports for a week, fix the legitimate sources, and only then switch to the enforcing header. Shipping a strict CSP straight to production is the fastest way to take your own site down.

Headers, file uploads, and content types

The X-Content-Type-Options: nosniff header matters most where a site serves user-supplied files. Without it, a browser may sniff an uploaded file as HTML or script and execute it, turning a file upload into stored XSS. This is where header defense meets file-format defense: the content type you declare has to match what the file actually is, and the browser has to be told to trust that declaration.

The safe pattern is to serve untrusted uploads from a separate origin, with nosniff, a restrictive Content-Security-Policy, and a Content-Disposition: attachment header so the browser downloads rather than renders them. Which formats can be smuggled through a permissive content-type handler is catalogued in our File-Format Abuse Atlas, which maps formats like SVG and HTML that execute script when a browser is tricked into rendering them.

The cross-origin isolation trio

The three Cross-Origin-* headers work together to put your page in an isolated context, which browsers require before granting access to high-precision timers and shared memory. More practically, they stop a range of cross-origin leaks:

  • Cross-Origin-Opener-Policy: same-origin severs the link between your window and any cross-origin page that opened it or that you open.
  • Cross-Origin-Embedder-Policy: require-corp forces every cross-origin resource to opt in, so nothing loads by accident.
  • Cross-Origin-Resource-Policy: same-origin stops other sites embedding your resources and reading them through side channels.

Set these where you handle sensitive data or need cross-origin isolation. On simpler sites they are lower priority than the core four, but they cost little to add.

How to use this reference

  1. Start with the core four. Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, and a framing control give you the most protection per line of config.
  2. Roll out CSP in report-only mode. Watch the violation reports, tune the sources, then enforce. Never ship a strict policy blind.
  3. Match headers to page sensitivity. Authenticated and sensitive pages need Cache-Control: no-store so their content never lands in a shared cache.
  4. Test the deployed headers. Use a scanner or the OWASP Secure Headers guidance to confirm what the server actually sends, since a header set in code but stripped by a proxy protects nothing.
  5. Treat headers as one layer. They reduce the blast radius of application bugs; they do not replace fixing the bugs.

Security headers are among the cheapest defenses you can deploy: a few lines of server configuration that the browser then enforces on every request. Get the core set right, roll CSP out carefully, and you close off whole categories of client-side attack before your application code ever runs.

Strict-Transport-Security in more depth

HSTS deserves a closer look because its value comes from a small mechanism with a large consequence, and because its main risk is operational rather than security. The header tells the browser to only ever contact this host over HTTPS for the duration set in max-age. Once a browser has seen it, a user who types the bare hostname, follows an http link, or is redirected to http never actually sends that plaintext request. The browser rewrites it to HTTPS internally before anything leaves the machine. That closes the small window an attacker on the network otherwise uses to intercept the first plaintext request and strip the connection down, the classic downgrade attack.

Three parts of the header matter. The max-age value, in seconds, sets how long the browser remembers the rule, and a year is a common production value. The includeSubDomains directive extends the rule to every subdomain, which is powerful and requires that every subdomain genuinely serve HTTPS, since any that cannot will become unreachable. The preload directive is a commitment of a different order. It asks to be baked into browsers' built-in preload lists, so that even a first-ever visit is forced to HTTPS with no prior header exchange. Preloading works, and it is hard to reverse quickly, so it is a decision to make only when you are confident every current and future subdomain will support HTTPS indefinitely.

Preload is a long-term promise

Adding your domain to the HSTS preload list forces HTTPS for every visitor, including first-time ones, and removal propagates through browser releases slowly. Do not preload until every subdomain reliably serves HTTPS and you intend to keep it that way. Start with a long max-age and includeSubDomains, confirm nothing breaks over weeks, and only then consider preload. Treat it as effectively permanent.

Reading a Content-Security-Policy directive by directive

A CSP is a list of directives, each naming the sources a given content type may load from, separated by semicolons. Reading one is a matter of taking each directive in turn and asking what it permits. The source keywords recur across directives, so learning them once unlocks the whole policy.

The common source expressions are worth knowing precisely. The keyword 'self' means the page's own origin, scheme and host and port included. The keyword 'none' forbids every source, used to switch a capability off entirely. A host source such as https://cdn.example allows that specific host. The data: scheme allows inline data URIs, which is convenient for images and dangerous for scripts. The keywords 'unsafe-inline' and 'unsafe-eval' re-enable inline scripts and dynamic evaluation, and each one substantially weakens the policy against injection. A nonce source, written as 'nonce-' followed by a per-response random value, allows only the inline scripts that carry the matching nonce attribute, which is how a strict policy permits its own inline code without opening the door to injected code.

The strength of a CSP comes down to how it handles script. A policy that allows 'unsafe-inline' for scripts gives an attacker who finds an injection point most of what they want, because injected inline script will run. A policy built on nonces or hashes, with no 'unsafe-inline', means an injected script has no valid nonce and is refused, so a cross-site scripting bug that would otherwise execute is reduced to a blocked violation report. This is the difference between a CSP that looks present in a scan and one that actually blunts an attack.

How to detect missing or weak headers

Verifying headers is straightforward, and the checks are concrete enough to automate across a whole site. The goal is to confirm both that the header is present and that its value is strong, since a weak value can be worse than none by implying protection that is not there.

  • Inspect the actual response headers a client receives, using browser developer tools or a command-line request, rather than trusting the application config, because a proxy or CDN can add, strip, or override headers in transit.
  • Check for the presence of the core set on every significant response: Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, and a framing control.
  • Read the CSP value for weakening keywords. The presence of 'unsafe-inline' or 'unsafe-eval' in script-src, or a missing object-src and base-uri, means the policy is softer than it looks.
  • Confirm Strict-Transport-Security has a substantial max-age and is served over HTTPS, since browsers ignore the header when it arrives over plain HTTP.
  • Verify sensitive, authenticated responses carry Cache-Control: no-store, and check that error pages and API responses are covered too, since these are easy to miss.
  • Watch for headers set correctly in one environment and missing in another, a common gap between local, staging, and production caused by differing proxy configurations.
Verify at the edge, not in the code

A header defined in your application means nothing if a reverse proxy or CDN strips or rewrites it before the browser sees it. Always test the headers on the response that actually reaches a client, from outside your infrastructure. A quick command-line request that prints the response headers, run against the public URL, is the ground truth. The application source code is only the intent.

Common mistakes and misconceptions

Several patterns turn header hardening into a false sense of safety.

"We set the headers in code, so we are covered." Not until you verify them on the wire. Proxies, load balancers, and CDNs routinely add or remove headers, and a duplicate or conflicting header can cause a browser to use a value you did not intend. The response the client receives is the only one that counts.

"A CSP is present, so XSS is handled." Only if the CSP is actually strict. A policy that allows 'unsafe-inline' for scripts provides little protection against injection, because injected inline script still runs. The presence of the header is not the protection; the exclusion of unsafe sources is.

"X-XSS-Protection should be enabled." That legacy header controlled a browser feature that has been removed from modern browsers and could itself introduce issues. The modern answer to reflected script is a strong CSP. Setting the old header does nothing useful today and can be dropped.

"More headers is always better." Headers can conflict and can break functionality. A Cross-Origin-Embedder-Policy of require-corp will stop cross-origin resources that have not opted in from loading at all, which breaks pages that legitimately embed third-party content. Each header is a deliberate choice with a cost, not a box to tick.

"HSTS protects the first request." Only with preload. Without preload, the browser has to see the header once over HTTPS before it starts enforcing, so the very first contact can still be plaintext. Preload closes that gap, at the cost of a long-term commitment.

How the header set evolved

The security header landscape has shifted as browsers standardised protections and retired improvised ones. Framing control began with X-Frame-Options, a single-purpose header against clickjacking, and has largely moved into CSP's frame-ancestors directive, which is more expressive and is the modern control while the older header remains for legacy browsers. Reflected-XSS filtering once lived in the X-XSS-Protection header, which browsers have since removed in favour of a strong CSP. Content-type sniffing defenses settled on the simple and durable X-Content-Type-Options: nosniff.

The most consequential arc is CSP itself, which grew from a blunt allow-list into a mechanism that supports nonces and hashes, letting sites run their own inline scripts safely while refusing injected ones. The cross-origin isolation headers arrived later, in response to side-channel attacks that made high-precision timers and shared memory risky, and they let a page opt into an isolated context where those capabilities are safe again. The direction across all of it is consistent: purpose-built, browser-enforced controls that express intent precisely, replacing the earlier generation of coarse toggles.

Response headers harden how the browser treats content, and the Set-Cookie header carries its own security attributes that round out the same defensive layer. They are worth setting alongside the core headers because a hardened page that ships a loosely scoped session cookie leaves the most valuable target exposed.

The Secure attribute tells the browser to send the cookie only over HTTPS, so a session cookie never leaks onto a plaintext connection. It pairs naturally with HSTS, which ensures the connection is HTTPS in the first place. The HttpOnly attribute hides the cookie from JavaScript, so that a cross-site scripting bug cannot read the session cookie through the document interface, which contains the damage of an injection that a CSP did not fully stop. The SameSite attribute controls whether the cookie is attached to cross-site requests, and setting it to Lax or Strict removes the automatic cross-site cookie flow that cross-site request forgery relies on, which is why it belongs in the same defensive picture as the response headers even though it is not one of them.

Setting these three on session cookies is close to free and closes gaps the response headers do not reach. A session cookie marked Secure, HttpOnly, and SameSite=Lax resists plaintext leakage, script theft, and a large share of cross-site request forgery at once. Treat cookie attributes as part of the same hardening pass, since the browser enforces them with the same reliability it enforces the headers, and the session cookie is usually the single most sensitive value a page handles.

Frequently asked questions

Which headers should I set first?

Start with the core four: Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options: nosniff, and a framing control (X-Frame-Options: DENY or CSP frame-ancestors 'none'). They deliver the most protection per line of configuration and cover the most common client-side attacks.

Is X-Frame-Options still needed if I use CSP frame-ancestors?

The frame-ancestors directive is the modern control and supersedes X-Frame-Options. Keeping the older header as well does no harm and covers legacy browsers that do not honour frame-ancestors, so setting both is a reasonable belt-and-braces choice.

Why does my strict CSP break the site?

Almost always because the page relies on inline scripts or styles, or on third-party sources the policy does not list. Deploy with Content-Security-Policy-Report-Only first, read the violation reports to see what the policy would block, add the legitimate sources or move to nonces, and only then switch to the enforcing header.

Does setting security headers replace fixing vulnerabilities?

No. Headers are defense in depth. A strong CSP limits the damage of a cross-site scripting bug, but the bug should still be fixed. Headers reduce blast radius; they do not remove the underlying flaw.

Should I enable X-XSS-Protection?

No. The header controlled a browser filter that modern browsers have removed, and it could introduce its own problems. Rely on a strong Content-Security-Policy for protection against reflected script instead.

What does X-Content-Type-Options: nosniff actually prevent?

It tells the browser not to guess a response's content type and to trust the declared Content-Type. Without it, a browser may sniff an uploaded or user-supplied file as HTML or script and execute it, turning a file upload into stored cross-site scripting. It matters most where a site serves untrusted files.

When do I need the cross-origin isolation headers?

Set Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy, and Cross-Origin-Resource-Policy where you handle sensitive data or need cross-origin isolation for features like high-precision timers and shared memory. On simpler sites they are lower priority than the core four, and require-corp in particular can break pages that embed third-party resources, so test before enforcing.

Sources & further reading

Sharetwitterlinkedin

Related guides