# Referrer-Policy: what leaks in the Referer header and how to stop it

> The Referer header tells every site, script and image on your page which URL a visitor came from, query string included. Pick the right Referrer-Policy value, set it on any server and verify it.

Updated 2026-09-25 · Security · HTML version: https://getreport.app/guides/referrer-policy-what-leaks-and-how-to-stop-it

Every time a browser leaves one of your pages, it tells the destination where the visitor came from. Without a policy, that message is the full address of your page: the path, the search term in the query string, the password-reset token, the order number. This guide explains what the `Referer` header carries, who receives it, which policy value to pick, and how to set it on every common server in about ten minutes.

## 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, and nothing is sent from HTTPS to HTTP.
- Use `same-origin` or `no-referrer` for areas whose URLs can carry something private: logged-in dashboards, password resets, search results with personal data.
- Override single links with `referrerpolicy="no-referrer"` or `rel="noreferrer"` when one page needs to be stricter than the site.
- The header beats the `<meta name="referrer">` tag: it covers every response, including files and error pages, and the [security headers checker](https://getreport.app/tools/security-headers) reads only the header.
- Verify in DevTools: Network, click a request to another site, Request Headers, `Referer`.

## Why the Referer header matters

The header is spelled `Referer` (a typo from 1996 that stuck) and the policy that controls it is spelled `Referrer-Policy`. What it carries is the URL of the page the request was made from. "Request" is the important word: not only the link the visitor clicks, but every image, script, font, iframe and tracking pixel your page loads from another domain. Each of those requests arrives at a third party with your page URL attached.

Take a page like `https://example-shop.hr/account/orders?order=48213&token=7f3a…`. With no policy in place, and in a browser old enough to use the old default, that entire string is sent to:

- every advertising or analytics script on the page, and every domain they in turn load from;
- the site behind any link the visitor clicks, and that site's analytics;
- the CDN serving your fonts and images.

Search terms are the everyday case. An internal search page at `/search?q=divorce+lawyer+zagreb` hands the query to every third party on the results page. Order confirmation pages, e-mail unsubscribe links, calendar links with an access key and "magic link" logins are the expensive cases: a token in the query string that reaches a third party can be replayed.

Since 2020 (Chrome 85) and 2021 (Firefox 87), the browser default is `strict-origin-when-cross-origin`, which trims cross-site referrers to the origin. So the leak is smaller than it used to be. It is not gone: older browsers, embedded webviews and some corporate builds still send the full URL, 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, documents your choice, and lets you go stricter than the default where it matters.

## How getReport checks it

> **Free tool:** [Security headers checker](https://getreport.app/tools/security-headers): Check HSTS, CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy and cookie flags on any site. Free, no signup, with a fix for every missing header.

The checker fetches the page like a browser, follows redirects, and reads the `Referrer-Policy` response header of the final page. Three things are worth knowing about how it judges the value:

- **Only the header counts.** A `<meta name="referrer">` tag in the HTML is not read, so a site that relies on the meta tag still gets the warning. That is deliberate: the meta tag does not cover files, redirects or error pages.
- **Any recognised value except `unsafe-url` passes.** That includes `no-referrer-when-downgrade`, the old browser default, which still sends the full URL to other HTTPS sites. Passing the check means "a policy is set and it is not the worst one", not "the best policy for your site is set". The table below helps you pick.
- **Lists are read the way browsers read them.** The header may contain several comma-separated values as a fallback for old browsers (`no-referrer, strict-origin-when-cross-origin`). The last value the browser recognises wins, and the check uses the same rule.

> **Check: Referrer-Policy header is set.** The referrer tells the next site which page of yours a visitor came from, including URL parameters such as search terms or tokens. A policy limits what leaves your site. Modern browsers already default to strict-origin-when-cross-origin; setting it explicitly covers older browsers and documents your choice.
>
> 1. Send the header: Referrer-Policy: strict-origin-when-cross-origin.
> 2. Use no-referrer or same-origin if URLs on your site can carry anything private.

The same panel grades the neighbouring headers. Two of them matter for the same reason, controlling what third-party code on your page can do:

> **Check: Permissions-Policy header is set.** The header switches off browser features this page never uses, such as camera, microphone or geolocation, so an injected script or a third-party iframe cannot turn them on.
>
> 1. Send the header: Permissions-Policy: camera=(), microphone=(), geolocation=() and list any feature you use with (self).

> **Check: Content-Security-Policy header is set.** A CSP blocks most cross-site scripting attacks by listing where scripts may load from. Without one, a single injected script can steal sessions or card data.
>
> 1. Start in report-only mode with Content-Security-Policy-Report-Only to see what would break.
> 2. Move to an enforcing policy once the report is quiet; keep "unsafe-inline" out of script-src.

![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](https://getreport.app/guides/img/referrer-policy-what-leaks-and-how-to-stop-it/referrer.webp "Missing and weak headers are marked in the header table; each has a finding with the fix underneath.")

The evidence line under the finding shows the raw value, for example `referrer-policy: (absent)` or `referrer-policy: unsafe-url`, so you can see exactly what the server sent.

## The policy values

| Value | Same-origin request | Cross-origin, HTTPS to HTTPS | HTTPS to HTTP |
| --- | --- | --- | --- |
| `no-referrer` | nothing | nothing | nothing |
| `same-origin` | full URL | nothing | nothing |
| `strict-origin` | origin only | origin only | nothing |
| `strict-origin-when-cross-origin` (browser default) | full URL | origin only | nothing |
| `origin` | origin only | origin only | origin only |
| `origin-when-cross-origin` | full URL | origin only | origin only |
| `no-referrer-when-downgrade` (old default) | full URL | full URL | nothing |
| `unsafe-url` | full URL | full URL | full URL |

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

### Which one to pick

**`strict-origin-when-cross-origin`** for almost every public site. Your own analytics still sees which page a visitor came from (same-origin requests get the full URL), and other sites learn only that the visitor came from your domain. This is what the check recommends and what the MDN [Referrer-Policy reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) documents as the default.

**`same-origin`** for logged-in areas, account pages, admin panels and internal tools. Other sites learn nothing at all, not even that the visitor came from you. Your own pages keep working as before.

**`no-referrer`** for pages whose URL is itself a secret: password resets, e-mail confirmation links, magic-link logins, unsubscribe pages, shared documents with a key in the path. Nothing is sent anywhere, including to your own site.

**`origin`** and **`strict-origin`** are the choice when you want other sites to see your domain (for partner attribution) but do not want even your own pages to see full URLs, for example when a third-party script runs on your site with the same origin. Rare.

Avoid `unsafe-url` and `no-referrer-when-downgrade`. The first sends everything everywhere; the second is what the web had before 2020 and the reason this guide exists.

### The analytics trade-off

Under `strict-origin-when-cross-origin`, a site you link to sees `https://example.com/` in its referrer reports rather than `https://example.com/blog/best-hiking-boots/`. If you run a publisher or affiliate site and your partners want page-level attribution, that is a real loss for them, not for you. Two ways to give it back selectively:

- put `referrerpolicy="no-referrer-when-downgrade"` on the specific outbound links where page-level detail is wanted, and keep the strict site-wide header;
- or use UTM parameters on those links, which carry the attribution explicitly and survive any referrer policy.

Your own analytics is not affected: requests to your analytics endpoint on the same origin get the full URL, and a hosted analytics script reads `location.href` from the page, not the referrer.

## Step by step

### 1. Decide per area, not per site

Most sites need two values: the default for public pages and a stricter one for the private area. Write down which paths hold anything sensitive in the URL. A typical list: `/account/`, `/admin/`, `/wp-admin/`, `/reset-password`, `/search`, `/unsubscribe`.

### 2. Set the header on the server

The header applies to every response the server sends, including images, PDFs and 404 pages, which a meta tag cannot reach. Use `always` (nginx) or `always set` (Apache) so error pages get it too.

**nginx**, in the `server` block, with a stricter value for the private area:

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

location /account/ {
    add_header Referrer-Policy "same-origin" always;
    # nginx add_header does not inherit: repeat every other header you set at server level here
}
```

**Apache**, in `.htaccess` at the site root or in the virtual host (`mod_headers` enabled):

```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**, in the Caddyfile:

```caddy
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", header name `Referrer-Policy`, value `strict-origin-when-cross-origin`. Add a second rule with a URI path filter for the private area. If the origin already sends the header, choose "Set" rather than "Add" so the browser does not receive two.

**Next.js**, in `next.config.js`:

```js
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' }],
      },
    ];
  },
};
```

### 3. Override single links and elements where needed

Per-element attributes win over the header and the meta tag. Two forms:

```html
<!-- One link that must not reveal where it was clicked from -->
<a href="https://partner.example/offer" referrerpolicy="no-referrer">Partner offer</a>

<!-- rel="noreferrer" also implies noopener; use it for links that open a new tab -->
<a href="https://partner.example/offer" target="_blank" rel="noreferrer">Partner offer</a>

<!-- Works on images, scripts and iframes too -->
<img src="https://cdn.example/pixel.gif" referrerpolicy="no-referrer" alt="">
```

`rel="noreferrer"` is the older, more widely supported form and also blocks `window.opener`, which is why WordPress adds `rel="noopener"` to new-tab links by default. `referrerpolicy` gives you the finer choice of value.

### 4. Use the meta tag only when you cannot touch headers

```html
<head>
  <meta name="referrer" content="strict-origin-when-cross-origin">
```

It must come early in `<head>`: requests started before the parser reaches it (a stylesheet in the first line, a preloaded font) go out under the header or the browser default. It does not cover files served without HTML, and the checker does not read it, so treat it as a stopgap on a platform where headers are out of reach.

> **Note:**
> If the header and the meta tag disagree, the meta tag wins for requests made after it is parsed, because it is applied later. Keep one source of truth. Two values that differ are the most common reason a site "has the header" but still leaks.

## Platform notes

### WordPress

If you can edit the server config, do it there; the header then also covers pages served from a cache plugin or the host's page cache, which PHP never touches. Without server access, a small must-use plugin works for everything PHP renders. Save this as `wp-content/mu-plugins/referrer-policy.php`:

```php
<?php
/**
 * Plugin Name: Referrer-Policy header
 */
add_action('send_headers', function () {
    header('Referrer-Policy: strict-origin-when-cross-origin');
});
```

Several security plugins also add the header from a settings screen; use one place only. WordPress itself has sent `Referrer-Policy: strict-origin-when-cross-origin` on `wp-admin` pages since version 4.9, so the admin, where URLs carry post ids, nonces and search terms, is covered before you add anything; the front end is not.

### Shopify, Wix, Squarespace

You cannot add response headers. Shopify sends its own security headers, including a referrer policy, on storefronts; check yours with the tool rather than assuming. Where the platform allows theme HTML edits, the meta tag in `theme.liquid` (or the equivalent header include) is the only option.

### Static sites

Netlify: a `_headers` file in the publish directory with `/*` then `  Referrer-Policy: strict-origin-when-cross-origin` indented on the next line. Vercel: the `headers` key in `vercel.json` with the same shape as the Next.js sample. GitHub Pages: no custom headers; use the meta tag.

## Verify

1. Re-run the [security headers checker](https://getreport.app/tools/security-headers). The finding should read "Referrer-Policy header is set" with your value on the evidence line.
2. From a terminal, from outside your network so a CDN or cache is included:

```bash
curl -sI https://example.com/ | grep -i referrer-policy
curl -sI https://example.com/account/ | grep -i referrer-policy
curl -sI https://example.com/does-not-exist | grep -i referrer-policy
```

All three should print a value; the third confirms error pages carry it too.

3. In the browser: open DevTools, Network tab, load a page, click any request to a different domain (a font, an analytics call) and look at Request Headers. `Referer` should show `https://example.com/` only. Click a same-origin request: the full page URL. Then follow an outbound link and check the first request on the destination the same way.
4. On a private page, `document.referrer` typed into the console on the *destination* page should be an empty string when the policy is `no-referrer` or `same-origin`.

## Common mistakes

- **Setting it in nginx at `server` level, then losing it in a `location` block.** `add_header` in a location replaces all inherited `add_header` lines, so a `location /account/` that sets one header silently drops the others. Repeat every header in each location that sets any.
- **Header and meta tag with different values.** The meta tag wins for later requests. Remove one.
- **Relying on the meta tag on a site with early-loading assets.** Fonts and stylesheets requested before the tag is parsed leak under the old rules. Move the header to the server.
- **`no-referrer` site-wide, then wondering why analytics lost all internal navigation paths.** Same-origin referrers are how many analytics tools attribute page-to-page flow. Use `strict-origin-when-cross-origin` publicly and reserve `no-referrer` for URLs that are secrets.
- **Treating the header as a fix for tokens in URLs.** It reduces where they travel; it does not stop them appearing in server logs, browser history and bookmarks. Move secrets out of the query string when you can (a POST body, a short-lived one-time code) and keep the policy as the safety net. Cookie hygiene is the other half: see [Cookie flags: Secure, HttpOnly, SameSite](https://getreport.app/guides/cookie-flags-explained), and for what third-party scripts may do once they are on your page, [Content-Security-Policy from report-only to enforced](https://getreport.app/guides/content-security-policy-rollout). The full header set is in [Security headers from zero to A](https://getreport.app/guides/security-headers-from-zero).
