Fix Missing X-Frame-Options — Clickjacking Protection
Without X-Frame-Options, attackers can embed your page in an invisible iframe and trick users into clicking things they cannot see. One header line fixes it.
What clickjacking looks like
An attacker creates a page that embeds your site in a transparent iframe positioned exactly over a button on their page. The user thinks they are clicking the attacker's button but they are actually clicking on your site — transferring money, changing settings, or approving actions.
The fix by stack
Nginx
add_header X-Frame-Options "SAMEORIGIN" always;
Apache
Header always set X-Frame-Options "SAMEORIGIN"
Express
const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'sameorigin' }));
Vercel (vercel.json)
{
"headers": [{ "source": "/(.*)", "headers": [
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" }
]}]
}
Cloudflare Transform Rules
Workers → Transform Rules → Response Headers → Add header: X-Frame-Options = SAMEORIGIN
SAMEORIGIN vs DENY
| Value | Allows | Use when |
|---|---|---|
| SAMEORIGIN | Your own domain can embed the page | You have admin panels or embedded pages on the same domain |
| DENY | No one can embed the page | Login pages, checkout, any page where embedding should never happen |
X-Frame-Options vs CSP frame-ancestors
CSP frame-ancestors is the modern replacement for X-Frame-Options. It is more flexible (supports multiple origins) and takes precedence when both are set. Use both for maximum browser compatibility:
# Nginx — both for full coverage add_header X-Frame-Options "SAMEORIGIN" always; add_header Content-Security-Policy "frame-ancestors 'self';" always;
When both are present, browsers that support CSP use frame-ancestors and ignore X-Frame-Options. Older browsers fall back to X-Frame-Options.
Scan your security headers live → HeadersFixer