Security Headers: nosniff, Referrer-Policy, Permissions-Policy
A few small response headers switch off risky browser defaults. Each one takes a single line of
server configuration and rarely breaks anything. This guide covers three of them:
X-Content-Type-Options, Referrer-Policy and Permissions-Policy. HSTS, Content Security Policy
and clickjacking protection have their own guides.
Why it matters
- X-Content-Type-Options. Browsers used to “sniff” a response and guess its type when the
declared
Content-Typelooked wrong. That let a file uploaded as an image or text be executed as a script or rendered as HTML.nosnifftells the browser to trust the declared type, and to refuse scripts and stylesheets whose type does not match. - Referrer-Policy. When a visitor follows a link or your page loads a third-party resource, the
browser sends a
Refererheader with the page address. Full URLs can contain search terms, account identifiers or reset tokens. A referrer policy limits how much of that leaves your site. - Permissions-Policy. It controls which powerful browser features (camera, microphone, geolocation, payment, USB and others) your pages and any embedded frames may use. Turning off what you do not use means a compromised third-party script or ad frame cannot request them.
What we check
| Check | Passes when |
|---|---|
| X-Content-Type-Options | the header is exactly nosniff |
| Referrer-Policy | the header is present and its effective value is not unsafe-url or no-referrer-when-downgrade |
| Permissions-Policy | the header is present |
A missing Referrer-Policy fails. unsafe-url and no-referrer-when-downgrade are reported as
“needs attention”, because both send the full URL, including the path and query string, to other
sites. When the header lists several comma-separated values, browsers use the last one they
support, and so do we.
We only check that Permissions-Policy is present. We do not judge which features it allows,
because that depends on what your site needs.
How to fix
Send these on every response:
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
Choosing a referrer policy
| Value | What other sites see |
|---|---|
no-referrer |
nothing |
same-origin |
nothing; your own pages get the full URL |
strict-origin |
only https://example.com/, and nothing over plain HTTP |
strict-origin-when-cross-origin |
the origin only; your own pages get the full URL |
strict-origin-when-cross-origin is a good default and is what modern browsers use when no policy
is set. Sending it explicitly makes the behavior consistent across browsers. Use no-referrer or
same-origin on pages whose URLs contain anything private.
Building a Permissions-Policy
feature=() disables a feature everywhere. feature=(self) allows it for your own pages only, and
feature=(self "https://maps.example") also allows one embedded origin. List the features you do
not use as (), and allow only the ones you actually need. For example, a store locator that
uses geolocation:
Permissions-Policy: camera=(), microphone=(), geolocation=(self), payment=(self)
Permissions-Policy replaced the older Feature-Policy header, which uses a different syntax.
Nginx
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
Remember that an add_header inside a location block replaces every header inherited from the
server block, so repeat them there or include a shared file.
Apache
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
Cloudflare: add the headers with a Response Header Transform Rule (Rules → Transform Rules → Modify Response Header).
After enabling nosniff
nosniff blocks scripts and stylesheets served with the wrong type. Make sure your server sends
text/javascript for .js and .mjs files and text/css for .css files. Static file servers
do this already; custom handlers and object storage buckets sometimes send
application/octet-stream or text/plain instead.
Headers you can drop
X-XSS-Protection is obsolete. Modern browsers have removed the filter it controlled, and in the
browsers that once had it, it could itself introduce problems. Remove it or set it to 0, and rely
on a Content Security Policy instead.
Further reading: MDN: Referrer-Policy and OWASP: HTTP Headers Cheat Sheet.