HTTP security headers: CSP, HSTS and the rest

Which HTTP security headers to set, with starting values, what each one breaks if you get it wrong and how to roll out CSP without taking the site down.

Check my site freeSee the plans
On this page 11 min read
  1. Summary: starting values and what can break
  2. Strict-Transport-Security (HSTS)
  3. Content-Security-Policy (CSP)
  4. X-Content-Type-Options
  5. Referrer-Policy
  6. Permissions-Policy
  7. X-Frame-Options or frame-ancestors
  8. Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy
  9. Headers you should no longer use
  10. A complete starter set
  11. How to check your site

The HTTP security headers that apply to almost every site are Strict-Transport-Security (forces HTTPS), Content-Security-Policy (limits scripts and who can show the page in an iframe), X-Content-Type-Options: nosniff, Referrer-Policy and Permissions-Policy. X-Frame-Options is still useful as a fallback for old browsers, and Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy isolate the page from other sites. X-XSS-Protection, Expect-CT and Feature-Policy should no longer be used. CSP takes the most work and breaks the most things; roll it out in report-only mode first.

This article covers browser behavior and the recommendations of MDN, OWASP and the W3C as of October 2026. It is part of the guide to website security basics, which covers HTTPS, exposed files, versions and cookies.

Summary: starting values and what can break

The values below are a common starting point, based on the references cited in each section. They are not the only correct ones: each site needs to adjust them to what it uses.

HeaderStarting valueWhat can break
Strict-Transport-Securitymax-age=63072000; includeSubDomainsSubdomains that do not have HTTPS yet become unreachable
Content-Security-PolicyPolicy with a nonce, object-src 'none', base-uri 'none' and frame-ancestorsInline scripts, onclick handlers, eval(), third-party widgets
X-Content-Type-OptionsnosniffScripts and CSS served with the wrong Content-Type
Referrer-Policystrict-origin-when-cross-originTools on another domain that rely on the full source path
Permissions-Policycamera=(), microphone=(), geolocation=()Features the site or a partner iframe actually uses
X-Frame-OptionsDENY or SAMEORIGINLegitimate display of the site inside iframes
Cross-Origin-Opener-Policysame-origin, or same-origin-allow-popups if there is login or payment in a pop-upOAuth and payment pop-ups that talk to the window that opened them
Cross-Origin-Resource-Policysame-siteFiles on your domain used by other sites

Strict-Transport-Security (HSTS)

HSTS tells the browser that the host can only be accessed over HTTPS for max-age seconds. Once it has received the header, the browser converts http requests to https on its own and does not let the user bypass certificate errors (MDN: Strict-Transport-Security). Three details matter:

  • The header only counts if it comes in an HTTPS response; sent over http, it is ignored.

  • It only protects from the second visit on, because the browser has to have received the header once. The browsers' preload list (preload) closes that gap.

  • includeSubDomains extends the rule to every subdomain, present and future.

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

Mozilla recommends at least six months and suggests two years (63072000) (MDN: TLS practical guide); ASVS 5.0 asks for at least one year and, from level 2, the inclusion of subdomains (ASVS 5.0, requirement 3.4.1). Rollout recommendation: start with a short max-age, check that everything works and only then raise it, because the browser will remember the rule for the whole period. OWASP warns that a misconfigured HSTS or a certificate problem can lock users out for the entire max-age (OWASP HTTP Headers Cheat Sheet).

To get on the preload list, the site needs a valid certificate, must redirect http to https on the same host, must have HTTPS on every subdomain and must send a max-age of at least one year with includeSubDomains and preload. Getting off the list is slow (it can take months to reach users), and other browsers are not obliged to follow the removal (hstspreload.org). Only apply if every subdomain, including internal ones, will stay on HTTPS in the long term.

Content-Security-Policy (CSP)

CSP is a list of rules that tells the browser where the page can load scripts, styles, images and iframes from, and where it can send data. Its main use is to reduce the impact of XSS (cross-site scripting, injecting a script into the page): even if an attacker manages to insert a <script>, the browser does not run it if it does not meet the policy. It is an extra layer and does not replace fixing XSS flaws (web.dev: strict CSP).

Domain allowlist or strict policy with a nonce

There are two ways to write the script rule. The older one is to list allowed domains (script-src 'self' https://cdn.example.com). The one MDN and web.dev recommend is the so-called strict CSP, based on a nonce or hash (MDN: CSP guide; web.dev: strict CSP). ASVS 5.0 accepts both forms at level 2, as long as the policy has object-src 'none' and base-uri 'none', and requires a per-response nonce or hash at level 3 (ASVS 5.0, requirement 3.4.3).

A nonce is a random value the server generates for each response, puts in the header and repeats in the nonce attribute of every legitimate <script>. The browser only runs scripts that carry the right value. The nonce has to be unpredictable and different on every response, and it should be inserted by the templating system, never added to every <script> on the page indiscriminately, so that an injected script does not get stamped too (MDN: CSP guide).

Content-Security-Policy:
  script-src 'nonce-R4nd0mPerResponse' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'self'

<script nonce="R4nd0mPerResponse" src="/js/app.js"></script>

'strict-dynamic' lets scripts created by an already trusted script run too. This allows third-party libraries and widgets to load their own files without you listing every domain (web.dev: strict CSP). For static or cached pages, web.dev recommends a hash instead of a nonce: 'sha256-...' calculated over the content of each inline script. A nonce saved into a cached page repeats for every visitor and becomes pointless.

frame-ancestors

The frame-ancestors directive defines which pages can show the site inside an <iframe>, which protects against clickjacking (when another site shows yours underneath fake buttons). MDN recommends 'none' for sites that do not need to be embedded (MDN: CSP guide). It does not inherit the value of default-src and does not work in a meta tag (MDN: frame-ancestors).

Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com

Report-only mode

The Content-Security-Policy-Report-Only header applies the same syntax without blocking anything: the browser only logs and sends the violations (MDN: Content-Security-Policy-Report-Only). Both headers can be sent together; in that case the normal policy is enforced and the report-only one just produces warnings, which lets you test a stricter version without touching the one in force (MDN: CSP guide).

Reports go to the address defined in the Reporting-Endpoints header and referenced by the report-to directive. The older report-uri directive is deprecated, but MDN recommends declaring both until report-to works in every browser (MDN: CSP guide; W3C: CSP Level 3).

Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only:
  script-src 'nonce-R4nd0mPerResponse' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
  report-uri https://example.com/csp-reports;
  report-to csp

How to roll out CSP without taking the site down

  1. List the scripts and resources of each page type (home, product, checkout, logged-in area) using the DevTools Network tab.

  2. Choose a nonce if pages are generated on the server for each request, or a hash if they are static or cached (web.dev).

  3. Adjust the code the policy will block: replace inline handlers such as onclick with addEventListener, replace javascript: links with event handlers and remove eval() (for example, in favor of JSON.parse()) (web.dev).

  4. Publish the policy in Content-Security-Policy-Report-Only with a reporting endpoint.

  5. Follow the reports long enough to cover every page type and flow, including checkout and seasonal campaigns, and fix or allow each legitimate origin.

  6. Switch to Content-Security-Policy and keep sending reports.

  7. Review the policy in the CSP Evaluator or in Lighthouse before and after the switch, as web.dev suggests.

What usually breaks

  • Inline scripts without a nonce or hash, including the ones plugins and themes print on the page.

  • Event attributes (onclick, onload) and javascript: links, which strict CSP blocks.

  • Libraries that use eval() or new Function(). Allowing them with 'unsafe-eval' weakens the policy (web.dev).

  • A nonce on a cached page, which repeats the value and either makes scripts fail or leaves the protection with no effect.

  • With a domain allowlist policy: any new origin (a pixel, a chat, a font) stays blocked until someone updates the list.

X-Content-Type-Options

With nosniff, the browser stops guessing the file type from its content and uses the declared Content-Type. Script requests are blocked if the type is not a JavaScript type, and stylesheet requests if it is not text/css. This prevents, for example, a user-uploaded file served as text from being run as HTML or script (MDN: X-Content-Type-Options). ASVS asks for the header on every response (requirement 3.4.4).

X-Content-Type-Options: nosniff

What breaks: files served with the wrong type, such as a .js delivered as text/plain. Before turning it on, check the Content-Type of your scripts and CSS, especially the ones generated dynamically.

Referrer-Policy

The Referer header tells the destination site where the visitor came from. If the address has data in the path or query string (a token, an email address, an order number), that data leaks to third parties. Referrer-Policy controls how much of that address is sent. When the site sets nothing, the browsers' current default is strict-origin-when-cross-origin: the full address to the same site, only the origin to other sites over HTTPS and nothing when the connection drops to HTTP (MDN: Referrer-Policy).

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

OWASP recommends declaring the header instead of relying on the browser default and uses this value as its example (OWASP HTTP Headers Cheat Sheet); the OWASP Secure Headers Project's list of values goes further and suggests no-referrer (headers_add.json). no-referrer is more restrictive, but it can get in the way of analytics tools that depend on the traffic source. Avoid unsafe-url, which sends the full address even to HTTP destinations.

Permissions-Policy

Permissions-Policy turns browser features such as camera, microphone, geolocation and payment on or off, for the page and for the iframes inside it. () turns the feature off everywhere; self allows it only for the same origin; a quoted origin allows it for that domain (MDN: Permissions-Policy).

Permissions-Policy: camera=(), microphone=(), geolocation=()
Permissions-Policy: geolocation=(self "https://maps.example.com"), microphone=()

OWASP's recommendation is to turn off everything the site does not use (OWASP HTTP Headers Cheat Sheet). MDN marks the header as experimental and says it does not work in some widely used browsers, so treat it as an extra protection, not the only barrier. What breaks: a partner iframe (map, video, payment) that needs a feature you turned off. On the iframe, the allow attribute grants the feature within the limit the header allows.

X-Frame-Options or frame-ancestors

X-Frame-Options was the first header against clickjacking. It accepts DENY (nobody can show the page in an iframe) and SAMEORIGIN (only pages from the same origin). The ALLOW-FROM value is obsolete and makes modern browsers ignore the whole header, and the header has no effect in a meta tag (MDN: X-Frame-Options).

The CSP frame-ancestors directive does the same with more options, such as allowing specific domains. The CSP specification says that when a policy with frame-ancestors is being enforced, the browser ignores X-Frame-Options (W3C: CSP Level 3, section 6.4.2.2). ASVS 5.0 treats X-Frame-Options as obsolete and asks for frame-ancestors (requirement 3.4.6). Recommendation: use frame-ancestors as the main rule and keep a consistent X-Frame-Options for old browsers. It does not get in the way of new ones, which already ignore it in that case.

Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy

Cross-Origin-Opener-Policy (COOP) decides whether your page shares its browsing context with windows from other sites that it opened or that opened it. With same-origin, a window from another domain is isolated and loses the window.opener reference. This breaks login (OAuth) and payment flows in pop-ups that send the result back to the original window; for those cases there is same-origin-allow-popups (MDN: Cross-Origin-Opener-Policy). ASVS asks for one of the two values on responses that render documents (requirement 3.4.8).

Cross-Origin-Opener-Policy: same-origin-allow-popups

Cross-Origin-Resource-Policy (CORP) goes on the resource, not the page. It says which origins can load that file in no-CORS requests, such as <img> and <script>: same-origin, same-site or cross-origin (MDN: Cross-Origin-Resource-Policy). Mozilla lists CORP among the protections against speculative side-channel attacks (MDN: practical guides), and OWASP suggests same-site. What breaks: images, fonts or scripts on your domain used by other sites, such as a widget you distribute. Those files need cross-origin.

The pair Cross-Origin-Embedder-Policy: require-corp with COOP same-origin turns on cross-origin isolation, which features such as SharedArrayBuffer need (MDN). COEP blocks resources from other origins that do not allow their use through CORP or CORS (OWASP). Opinion: on a company website or store that does not need this isolation, COEP usually costs more (blocked ads, videos and widgets) than it delivers.

Headers you should no longer use

HeaderStatusWhat to do
X-XSS-ProtectionNot part of any specification and obsolete. MDN warns that the filter can create XSS flaws on sites that did not have them (MDN)Do not send it, or send 0 to turn the filter off, as OWASP suggests; protect with CSP
Expect-CTOnly Chromium implemented it, and it has considered it obsolete since version 107, because it already requires Certificate Transparency by default (MDN)Remove it
Feature-PolicyRenamed to Permissions-Policy, with a different syntax. In Chrome it only works as an alias, and Permissions-Policy wins when both are sent (Chrome for Developers)Migrate to Permissions-Policy
Public-Key-PinsOWASP recommends not using it and removing it from production (OWASP)Remove it

A complete starter set

An example for a dynamically served site that is not embedded by other sites and has payment in a pop-up. How you configure it depends on the server (nginx, Apache), the CDN or the framework; the content of the headers is the same.

Strict-Transport-Security: max-age=63072000; includeSubDomains
Content-Security-Policy: script-src 'nonce-{generated per response}' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; report-to csp
Reporting-Endpoints: csp="https://example.com/csp-reports"
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Frame-Options: DENY
Cross-Origin-Opener-Policy: same-origin-allow-popups

For comparison, the OWASP Secure Headers Project publishes a stricter list, updated often, meant as a maximum reference and not as a configuration to copy without testing (headers_add.json).

How to check your site

In the terminal, curl -sI https://example.com/ shows the headers of a response. Repeat it for an inner page, an image file and an error response, because servers and CDNs do not always apply the same headers to everything. Mozilla's HTTP Observatory gives a score and explains each item. The WebVyze audit flags missing HSTS, CSP, X-Content-Type-Options and Referrer-Policy, a CSP that exists but does not stop injected scripts and a page that can be embedded by other sites, each with a fix guide. For stores, the CSP on the payment page also counts for PCI DSS; the article on requirements 6.4.3 and 11.6.1 explains how, and WebVyze's paid plans keep an inventory of scripts on checkout pages. WebVyze only reads public pages and does not change anything on the site.

Frequently asked questions

Can I put the CSP in a meta tag instead of the header?
Yes, with limits. The specification ignores the frame-ancestors, report-uri and sandbox directives when the policy comes from a meta tag, and report-only mode (Content-Security-Policy-Report-Only) does not work in a meta tag. For a complete policy, use the header.
Do the headers need to be on every response or only on HTML pages?
It depends on the header. ASVS 5.0 asks for HSTS, nosniff and frame-ancestors on every response and Cross-Origin-Opener-Policy on responses that render a document, such as HTML pages. Setting them on the server or CDN for every response is usually the simplest route.
Does removing the Server and X-Powered-By headers improve security?
It makes it harder to identify the technology in use, and OWASP recommends removing these headers or leaving them without useful information. It does not stop someone from finding out the technology by other means, and it does not replace updates.
How do I receive CSP violation reports?
The browser sends a JSON POST to the address declared in Reporting-Endpoints (report-to directive) or in report-uri. You need an endpoint that accepts and stores these submissions: a simple service of your own or a third-party tool.
About the author

Daniel Paz

WordPress, performance and SEO specialist, founder of WebOption

Daniel Paz is a WordPress, performance and SEO specialist, the founder of WebOption and an international speaker. With more than 18 years of experience in web development, he helps companies turn their websites into fast, efficient, results-driven platforms.

See what your site needs to fix.

A free check by law, with the cause and the fix guide for each problem.

Check my site freeCreate a free account

Keep reading

Partners

Partner benefits with one coupon.

WPLIGHT

Partner links. We may earn a commission at no extra cost to you.