Skip to content

SecurityPart of: Security headers

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

strict-origin-when-cross-origin sends your full URL to your own pages and only your domain to other sites. What leaks without it, how to set Referrer-Policy, and its companion Permissions-Policy.

getReport teamUpdated 4 Oct 202613 min read

strict-origin-when-cross-origin is a Referrer-Policy value that sends the full page URL on requests within your own site, only your origin (https://example.com/) to other sites, and nothing at all from HTTPS to HTTP. It is the browser default today and the value most sites should send explicitly: it keeps search terms, order numbers and tokens in your URLs away from third parties without breaking your own analytics. This guide explains what the Referer header carries, when a stricter value is worth it, how to set the header on any server, and the companion privacy header, Permissions-Policy. Both are part of the six headers in HTTP security headers from zero to A.

Quick answer

  • Send Referrer-Policy: strict-origin-when-cross-origin from the server. Other sites then see only https://example.com/, your own pages still see the full URL.
  • Use same-origin or no-referrer for areas whose URLs can carry something private: logged-in dashboards, password resets, internal search.
  • Override single links with referrerpolicy="no-referrer" or rel="noreferrer".
  • Add Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=() so no script or iframe on your page can ask for features you never use.
  • Check both with the security headers checker; it reads the headers, not <meta> tags.

What leaks in the Referer header

The header is spelled Referer (a typo from 1996 that stuck); the policy that controls it is spelled Referrer-Policy. It carries the URL of the page a request was made from, and "request" is the important word: not only the link a visitor clicks, but every image, script, font, iframe and tracking pixel your page loads from another domain.

Take https://example-shop.hr/account/orders?order=48213&token=7f3a…. Under the old default, that entire string went to every analytics and ad script on the page, every domain they load from, the CDN serving your fonts, and the site behind any link the visitor clicks. Internal search is the everyday case: /search?q=divorce+lawyer+zagreb hands the query to every third party on the results page. Order pages, unsubscribe links and magic-link logins are the expensive cases, because a token that reaches a third party can be replayed.

Since Chrome 85 (2020), Firefox 87 (2021) and Safari, the browser default has been strict-origin-when-cross-origin, so the leak is smaller than it used to be. It is not gone: embedded webviews and old browsers still differ, and any page can widen the policy with a meta tag or an attribute, on purpose or by accident. An explicit header pins the behaviour and lets you go stricter where it matters.

The Referrer-Policy values

ValueSame-origin requestCross-origin, HTTPS to HTTPSHTTPS to HTTP
no-referrernothingnothingnothing
same-originfull URLnothingnothing
strict-originorigin onlyorigin onlynothing
strict-origin-when-cross-origin (default)full URLorigin onlynothing
originorigin onlyorigin onlyorigin only
origin-when-cross-originfull URLorigin onlyorigin only
no-referrer-when-downgrade (old default)full URLfull URLnothing
unsafe-urlfull URLfull URLfull URL

"Origin only" means scheme and host, https://example.com/. "Full URL" includes path and query string, but never the #fragment or a username and password.

strict-origin-when-cross-origin fits almost every public site. Your own analytics still sees page-to-page paths, and other sites learn only that the visitor came from your domain. It is what the check recommends and what MDN's Referrer-Policy reference documents as the default.

same-origin suits logged-in areas, account pages and admin panels: other sites learn nothing at all. no-referrer suits pages whose URL is itself a secret, such as password resets and shared documents with a key in the path. Avoid unsafe-url and no-referrer-when-downgrade, which send full URLs to every HTTPS site.

The analytics trade-off. Sites you link to see https://example.com/ rather than the exact article in their referrer reports. If a partner needs page-level attribution, add referrerpolicy="no-referrer-when-downgrade" to those specific links, or use UTM parameters, which survive any policy. Your own analytics is unaffected.

How getReport checks it

The checker fetches the page like a browser, follows redirects and reads the response headers of the final page:

  • Only the header counts. A <meta name="referrer"> tag is not read, because it does not cover files, redirects or error pages.
  • Any recognised Referrer-Policy value except unsafe-url passes, including no-referrer-when-downgrade. Passing means "a policy is set and it is not the worst one"; the table above helps you pick the best one.
  • Lists are read the way browsers read them. In no-referrer, strict-origin-when-cross-origin, the last value the browser recognises wins, and the check uses the same rule.
  • Permissions-Policy is a presence check. Any non-empty value passes and the evidence line shows it. There is no universally right list: a video-call app needs camera=(self), a blog camera=().

The security headers panel on a page with no Referrer-Policy: the header table lists each header with its value, the missing Referrer-Policy and Permissions-Policy rows marked amber, and the Server header marked for a version number
Missing and weak headers are marked in the header table; each has a finding with the fix underneath.

Set Referrer-Policy on your server

Most sites need two values: the default for public pages and a stricter one for the private area. The header covers every response, including images, PDFs and 404 pages.

nginx, in the server block:

nginx
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

location /account/ {
    add_header Referrer-Policy "same-origin" always;
    # add_header in a location replaces the inherited ones: repeat every other header here
}

Apache, in .htaccess or the virtual host (mod_headers):

Apache
Header always set Referrer-Policy "strict-origin-when-cross-origin"

<If "%{REQUEST_URI} =~ m#^/account/#">
    Header always set Referrer-Policy "same-origin"
</If>

Caddy:

Caddyfile
example.com {
    header Referrer-Policy "strict-origin-when-cross-origin"
    header /account/* Referrer-Policy "same-origin"
    reverse_proxy app:3000
}

Cloudflare: Rules → Transform Rules → Modify Response Header → "Set static", Referrer-Policy = strict-origin-when-cross-origin; add a second rule with a path filter for the private area. Choose "Set", not "Add", so the browser does not receive two.

Next.js, in next.config.js:

JavaScript
module.exports = {
  async headers() {
    return [
      { source: '/:path*', headers: [{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }] },
      { source: '/account/:path*', headers: [{ key: 'Referrer-Policy', value: 'same-origin' }] },
    ];
  },
};

Single links and elements can go stricter than the site. Attributes win over the header:

HTML
<a href="https://partner.example/offer" referrerpolicy="no-referrer">Partner offer</a>
<a href="https://partner.example/offer" target="_blank" rel="noreferrer">Partner offer</a>
<img src="https://cdn.example/pixel.gif" referrerpolicy="no-referrer" alt="">

rel="noreferrer" also implies noopener. The meta tag, <meta name="referrer" content="strict-origin-when-cross-origin">, is a stopgap for platforms where headers are out of reach: requests started before the parser reaches it go out under the old rules, and if header and tag disagree, the tag wins for later requests. Keep one source.

Permissions-Policy, the companion header

Referrer-Policy limits what your page tells other sites. Permissions-Policy limits what code on your page may do. Every script that runs in your page, including the analytics tag, the chat widget and whatever they load, has the same powers as your own code. A compromised one can call getUserMedia(), and the permission prompt shows your domain. The header says in advance which browser features your site will never use, so the call fails with no prompt.

Text
Permissions-Policy: camera=(), microphone=(), geolocation=(self), payment=(self "https://checkout.example")
AllowlistMeaning
()Nobody: the top page and every iframe are denied.
(self)Your own origin and same-origin iframes.
(self "https://a.example")Your origin plus the quoted origins, if their <iframe> also carries allow.
*Any origin. Rarely what you want.

A feature you do not mention keeps its browser default, which for most features is self; unknown names are ignored. Starting values:

  • Content site: camera=(), microphone=(), geolocation=(), payment=(), usb=(), serial=(), bluetooth=(), display-capture=(). Leave fullscreen and autoplay alone so YouTube and Vimeo embeds keep working.
  • Shop with a hosted payment iframe: payment=(self "https://checkout.example") with the provider's origin from the iframe src, and allow="payment" on the iframe. Denying payment makes Apple Pay and Google Pay buttons disappear.
  • Store finder: geolocation=(self).

Set it next to Referrer-Policy: add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always; in nginx, Header always set Permissions-Policy "…" in Apache, or another entry in the lists above. In Chrome, DevTools → Application → Frames → top lists each feature as allowed or disabled.

Two history notes. Feature-Policy was the first version with a different syntax (camera 'none'); copying that syntax into Permissions-Policy is silently ignored. And interest-cohort=() (FLoC) and browsing-topics=() (the Topics API) are leftovers: Google withdrew FLoC in 2022 and retired Topics with most Privacy Sandbox APIs in October 2025, so both entries can go.

The header is defence in depth. Per MDN's compatibility data, Chromium browsers enforce it; Firefox and Safari support the iframe allow attribute but not the response header yet.

Cross-Origin-Opener-Policy, COEP and CORP

Three more Cross-Origin-* headers keep your pages apart from other sites in the browser. They matter mainly for web apps that handle sensitive data in pop-ups or need cross-origin isolation; a typical content site or shop can skip them, and getReport does not grade them.

HeaderSent onWhat it doesTypical value
Cross-Origin-Opener-Policy (COOP)Your pagesCuts the link between your page and windows from other sites that open it or that it opens, so they cannot reach it through window.opener or a pop-up handle.same-origin-allow-popups, or same-origin if you open no third-party pop-ups
Cross-Origin-Embedder-Policy (COEP)Your pagesOnly lets the page load cross-origin images, scripts and frames that agree to it, through a CORP header or CORS.Leave it off unless you need isolation; then require-corp or credentialless
Cross-Origin-Resource-Policy (CORP)Your filesStops other sites from embedding the file with a plain <img> or <script> tag.same-site for private files, cross-origin for files others may embed

Cross-Origin-Opener-Policy is the one most sites can use. With same-origin, a cross-origin page that opens yours, or that yours opens, ends up in a separate browsing context group: neither side can script the other, and the browser can keep them in separate processes. That blocks a family of cross-window attacks. The catch is pop-ups: sign-in with Google, PayPal and other payment or OAuth windows talk back to the page that opened them, and same-origin cuts that line. same-origin-allow-popups keeps pop-ups you open working while still protecting your page from whoever opened it. The default, unsafe-none, applies when no header is sent.

Cross-Origin-Embedder-Policy with require-corp blocks every cross-origin subresource loaded without CORS unless it sends a Cross-Origin-Resource-Policy header allowing it. Ads, embedded videos and third-party images often do not, so turning it on breaks them. credentialless loads such files anyway but without cookies, which breaks less.

Cross-Origin-Resource-Policy is sent on the files themselves. same-site lets only your own site and its subdomains embed them, which the OWASP HTTP Headers Cheat Sheet recommends for resources that are not meant for others. Public files that other sites embed, such as fonts and images on a shared CDN, need cross-origin.

Cross-origin isolation. A page that sends Cross-Origin-Opener-Policy: same-origin together with Cross-Origin-Embedder-Policy: require-corp (or credentialless) is cross-origin isolated: window.crossOriginIsolated is true, and the browser unlocks SharedArrayBuffer, precise performance.now() timers and performance.measureUserAgentSpecificMemory(). In-browser video and image editors, WebAssembly apps that use threads, and emulators need it. MDN's pages on the three headers document the values and this rule:

Text
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Set them like the headers above, with add_header in nginx or Header always set in Apache, on the pages that need them only, and test every embed and pop-up afterwards.

Verify

  1. Re-run the security headers checker: "Referrer-Policy header is set" and "Permissions-Policy header is set", with your values on the evidence lines.
  2. From outside your network, so the CDN is included:
Shell
curl -sI https://example.com/ | grep -i -E "referrer-policy|permissions-policy"
curl -sI https://example.com/does-not-exist | grep -i referrer-policy

One line per header; the second command confirms error pages carry it too.

  1. In DevTools → Network, click a request to another domain: Referer shows https://example.com/ only. A same-origin request shows the full URL.
  2. Video embeds still go fullscreen and the checkout still shows its payment buttons.

Common mistakes

  • Losing headers in an nginx location block. One add_header there drops all inherited ones. Repeat them or use an include file.
  • Header and meta tag with different values. The meta tag wins for later requests. Remove one.
  • no-referrer site-wide. Your analytics loses internal navigation paths. Reserve it for URLs that are secrets.
  • Treating the header as a fix for tokens in URLs. Tokens still land in logs, history and bookmarks. Move secrets out of the query string; see also cookie flags.
  • fullscreen=() on a site with video embeds. The player's fullscreen button stops working.
  • Treating Permissions-Policy as a substitute for CSP. A malicious script still loads; it just cannot open the camera. Stopping it loading is the job of a Content Security Policy.

Questions people ask

Is strict-origin-when-cross-origin the browser default?

Yes. Chrome switched to it in version 85 in 2020, Firefox in version 87 in 2021, and Safari uses it too, so a page with no policy already behaves this way in current browsers. Send the header anyway: it pins the behaviour for embedded webviews and older browsers, documents your choice, and stops a stray meta tag quietly widening what your pages share.

Does strict-origin-when-cross-origin affect my analytics?

Not your own. Same-origin requests still carry the full URL, and analytics scripts read the page address directly. What changes is what other sites see: they get https://example.com/ instead of the exact page a visitor came from. If a partner needs page-level attribution, add UTM parameters to those links or set referrerpolicy on them individually.

What is the difference between Referrer-Policy and Permissions-Policy?

Referrer-Policy controls how much of your page address the browser sends to other sites in the Referer header. Permissions-Policy controls which browser features, such as the camera, microphone, location or payments, code on your page and embedded iframes may use. One limits what leaks out; the other limits what third-party code can do. Most sites should send both.

Which browsers support the Permissions-Policy header?

Chrome, Edge and other Chromium browsers enforce the Permissions-Policy response header. Firefox and Safari support the same policy through the iframe allow attribute but, according to MDN's compatibility data, not the header itself yet. Send it anyway: it protects most visitors today, costs nothing for the rest, and documents which features your site uses.

Check your site before and after Check