How to Set Security Headers: X-Frame-Options, nosniff, Referrer-Policy and Permissions-Policy
By the IDEAVALU team · Updated
A few lines of response headers turn on defenses built into the browser. Many indie sites launch without any of them.
What each header does
Each one blocks something different.
- X-Frame-Options (or CSP frame-ancestors): stops clickjacking, where another site embeds yours in an invisible iframe and tricks visitors into clicking buttons.
- X-Content-Type-Options: nosniff: stops the browser from guessing a file type and executing, say, an image as a script.
- Referrer-Policy: decides how much of the URL is sent to linked sites. With strict-origin-when-cross-origin, other sites receive only your domain, so IDs and tokens in your URLs do not leak.
- Permissions-Policy: turns off browser features you do not use, such as the camera, microphone and location, so embedded third-party scripts cannot use them either.
Check it yourself
This command shows the main headers at once.
Show the main security headers
curl -sI https://example.com | grep -iE "x-frame-options|x-content-type-options|referrer-policy|permissions-policy|content-security-policy|strict-transport-security"
Version disclosure and security.txt
A version such as "nginx/1.18.0" or "PHP/7.4" in the Server or X-Powered-By header helps attackers target known bugs in old releases. Hide it, and update the software itself.
Publishing a contact in /.well-known/security.txt tells anyone who finds a vulnerability where to report it (RFC 9116). At minimum, include the Contact and Expires lines.
Example public/.well-known/security.txt
Contact: mailto:security@example.com Expires: 2027-12-31T23:59:59.000Z Preferred-Languages: en
How to fix
Clickjacking protection
Forbid embedding in other sites with X-Frame-Options (or CSP frame-ancestors).
Next.js
// next.config.mjs
const securityHeaders = [
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]
export default {
async headers() {
return [{ source: '/:path*', headers: securityHeaders }]
},
}Static sites, Vite and others on Vercel
// vercel.json (for non-Next.js sites on Vercel)
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
]
}Netlify and Cloudflare Pages
# public/_headers (Netlify, Cloudflare Pages) /* Strict-Transport-Security: max-age=63072000; includeSubDomains X-Frame-Options: SAMEORIGIN X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=()
One config fixes HSTS, clickjacking, nosniff, Referrer-Policy and Permissions-Policy together. If your app uses the camera or location, change that feature to self in Permissions-Policy.
X-Content-Type-Options
Add X-Content-Type-Options: nosniff. The config is the same as above.
Referrer-Policy
With Referrer-Policy: strict-origin-when-cross-origin, other sites receive only your domain. The config is the same as above.
Permissions-Policy
Disable browser features you do not use with Permissions-Policy. The config is the same as above.
Software version disclosure
Remove version numbers from response headers.
nginx
server_tokens off;
Express
app.disable('x-powered-by')PHP (php.ini)
expose_php = Off
FAQ
- Should I use X-Frame-Options or frame-ancestors?
- Modern browsers give CSP frame-ancestors priority. If you do not use a CSP, X-Frame-Options: SAMEORIGIN is enough. Setting both is fine.