X-Frame-Options header and clickjacking
Controls whether other sites may show your page inside a frame, which is how clickjacking attacks work.
Updated 3 November 2026
Check your site's X-Frame-Options header
We send one normal request (following redirects) and grade the X-Frame-Options header on the final response.
Recommended value
X-Frame-Options: DENY
| Value / directive | What it does |
|---|---|
| DENY | The page cannot be framed by anyone, including your own site. |
| SAMEORIGIN | Only pages on the same origin may frame it. |
| ALLOW-FROM uri | Obsolete. Current browsers ignore it, which leaves the page frameable. Use CSP frame-ancestors instead. |
What is clickjacking?
Clickjacking loads your page in an invisible iframe on top of an attacker's page. The victim thinks they are clicking "Play video" but actually clicks "Delete account", "Transfer" or "Grant access" on your site, with their own logged-in session. Because the click is real, CSRF tokens do not help.
X-Frame-Options: DENY tells the browser to refuse to render your page inside a frame at all. SAMEORIGIN allows your own site to frame it, which you need if you embed your own pages (for example an admin preview).
X-Frame-Options vs CSP frame-ancestors
CSP's frame-ancestors directive is the modern replacement. It can list several allowed origins, which X-Frame-Options cannot:
Content-Security-Policy: frame-ancestors 'self' https://partner.example
When a response has both, modern browsers obey frame-ancestors and ignore X-Frame-Options. Sending both is still sensible: old browsers only understand X-Frame-Options. Remember that frame-ancestors does not work in a <meta> tag, and neither does X-Frame-Options.
When you do need framing
If you build an embeddable widget, do not drop protection everywhere. Serve the embeddable route without the header (or with a frame-ancestors allowlist of customer origins) and keep DENY on login, settings and payment pages.
How to set X-Frame-Options
nginx
# inside your server { } block
add_header X-Frame-Options "DENY" always;
Apache
# .htaccess or virtual host (needs mod_headers)
Header always set X-Frame-Options "DENY"
Laravel middleware
// app/Http/Middleware/SecurityHeaders.php
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'DENY');
return $response;
}
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\App\Http\Middleware\SecurityHeaders::class);
})
Express (Node.js)
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
next();
});
// or: app.use(require('helmet')()) for a sensible default set
Cloudflare
Rules → Transform Rules → Modify Response Header → Create rule.
Match: all incoming requests (or your hostname).
Then: Set static → Header name: X-Frame-Options → Value: DENY
In nginx, an add_header inside a location block discards every add_header inherited from the server block. Repeat your security headers there, or use an include file. The always flag also sends the header on error responses.
Common mistakes
-
Using
ALLOW-FROM. Chrome and Safari never supported it and Firefox removed it, so the page is frameable everywhere. -
Setting the header in an HTML
<meta>tag. Browsers ignore X-Frame-Options in meta tags. - Protecting the home page but not the pages with dangerous buttons (account deletion, OAuth consent, payments).
-
Sending
DENYandSAMEORIGINfrom two different layers. Browsers may treat the conflicting header as invalid.
FAQ
Should I use DENY or SAMEORIGIN?
Use DENY unless your own site frames its pages. SAMEORIGIN is fine when you need same-site previews or embeds.
Is X-Frame-Options deprecated?
It is superseded by CSP frame-ancestors, but still worth sending for older browsers. Browsers that support frame-ancestors use it first.
Can I allow one other site to frame my page?
Not with X-Frame-Options (ALLOW-FROM no longer works). Use Content-Security-Policy: frame-ancestors with the allowed origin.
Related guides
Headers are one of 30+ things attackers check
The free StackShield scan also checks for exposed .env files, debug mode, leaked tooling, weak TLS and more, from the outside, in about a minute.
Run a free scan