X-Frame-Options Reference
DENY, SAMEORIGIN, ALLOW-FROM: what each directive does, browser support, and how to set the header on your stack.
X-Frame-Options is an HTTP response header that tells a browser whether a page may be rendered inside a <frame>, <iframe>, <embed>, or <object>. Microsoft introduced it in Internet Explorer 8 as the first header-based defense against clickjacking. RFC 7034 (an Informational RFC, not a standard) documented it in 2013; today the HTML Living Standard defines how browsers process it. The Content Security Policy frame-ancestors directive has superseded it, but every current browser still enforces it, and sending both headers remains a sensible baseline.
The header takes one value, matched case-insensitively. DENY and SAMEORIGIN work everywhere. ALLOW-FROM works in no current browser.
DENY
DENY blocks all framing. No page can embed it, including pages on your own origin.
X-Frame-Options: DENYThis is the right choice for most sites. If a page has no reason to appear in an iframe (true of most pages), use DENY.
SAMEORIGIN
SAMEORIGIN allows framing only when every ancestor frame, up to the top-level page, has the same origin as the framed page. Pages on your origin can embed it; pages on any other origin cannot.
X-Frame-Options: SAMEORIGINUse it when your application frames its own pages: dashboards embedding widgets, live previews in an editor, page builders that render your content in an iframe.
The every-ancestor check is what the HTML standard requires, and what Chrome (since 61) and Firefox (since 59) do. Older browsers compared only against the top-level page, as RFC 7034 describes, so a cross-origin page framed inside your site could in turn frame your SAMEORIGIN pages.
Note that subdomains are different origins: https://app.example.com and https://admin.example.com cannot frame each other under SAMEORIGIN. If you need cross-subdomain framing, use CSP frame-ancestors with explicit origins instead.
ALLOW-FROM
ALLOW-FROM was meant to allow framing by one named origin, so a single trusted partner could embed your page while everyone else was blocked.
X-Frame-Options: ALLOW-FROM https://trusted.example.comDo not use this directive. It is obsolete. Chrome, Safari and Chromium-based Edge never supported it, and Firefox removed it in version 70 (October 2019). The HTML standard says it is not to be implemented: a lone ALLOW-FROM value is treated as if the header were absent, so the page has no framing protection at all.
frame-ancestors https://partner.com to allow specific origins.Send exactly one value. Browsers that follow the HTML standard treat repeated headers and comma-separated values the same way: a repeated identical value (SAMEORIGIN, SAMEORIGIN) behaves like one, but any mix that includes DENY or SAMEORIGIN (DENY, SAMEORIGIN) blocks all framing. This usually happens when the app and a proxy or CDN both add the header.
Browser support
| Browser | DENY / SAMEORIGIN | ALLOW-FROM |
|---|---|---|
| Chrome | 4+ | Never supported |
| Firefox | 4+ | 18 to 69; removed in 70 |
| Safari | 4+ | Never supported |
| Edge | 12+ | Legacy Edge 12 to 18 only; not in Chromium Edge (79+) |
| Internet Explorer | IE8+ | IE8 to 11 (retired) |
Sources: MDN browser compatibility data and Can I Use, October 2026. Mobile versions of these browsers match their desktop counterparts. Only retired browsers (Internet Explorer, legacy Edge, Firefox 69 and older) ever honored ALLOW-FROM.
How to set X-Frame-Options
Set the header on the HTTP response from your server, CDN, or application framework. Each one-liner below links to a full guide where one exists.
| Platform | Snippet |
|---|---|
| NGINX | add_header X-Frame-Options "DENY" always; |
| Apache | Header always set X-Frame-Options "DENY" |
| Cloudflare | Rules → Response Header Transform Rule → Set static: X-Frame-Options = DENY |
| WordPress | add_action('send_headers', function () { header('X-Frame-Options: DENY'); }); |
| Express.js | app.use((req, res, next) => { res.setHeader('X-Frame-Options', 'DENY'); next(); }); |
| IIS | <add name="X-Frame-Options" value="DENY" /> inside <system.webServer><httpProtocol><customHeaders> in web.config |
The meta tag that does not work
A common mistake is trying to set the header from HTML with a <meta> tag:
<!-- This has no effect. Do not do this. -->
<meta http-equiv="X-Frame-Options" content="deny">X-Frame-Options only works as an HTTP response header; browsers ignore it in a <meta> tag. CSP frame-ancestors is ignored in a meta tag too. Configure your server or CDN to send real HTTP headers.
frame-ancestors: the modern replacement
X-Frame-Options has been superseded by the Content Security Policy frame-ancestors directive, which MDN lists as Baseline Widely Available since January 2018. It covers everything XFO does, and more:
frame-ancestors 'none'is similar to XFODENY.frame-ancestors 'self'is similar to XFOSAMEORIGIN.- It supports multiple explicit origins: the thing
ALLOW-FROMpromised but never delivered. - It has always checked every ancestor in a nested frame chain. XFO
SAMEORIGINonly does in modern browsers.
Content-Security-Policy: frame-ancestors 'self' https://trusted.example.comframe-ancestors does not fall back to default-src. You must set frame-ancestors explicitly. A policy of default-src 'self' does nothing to prevent clickjacking.
If a response carries an enforced CSP with a frame-ancestors directive, browsers ignore X-Frame-Options entirely, whatever either header says. So frame-ancestors * plus X-Frame-Options: DENY leaves the page frameable by any site. A Content-Security-Policy-Report-Only header does not override X-Frame-Options; only an enforced policy does. Browsers without frame-ancestors support (Internet Explorer) rely on X-Frame-Options alone.
Keep XFO as a legacy fallback and make sure the two headers agree. For most sites:
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENYRead next
- NGINX clickjacking protection
- Apache clickjacking protection
- Cloudflare clickjacking protection
- WordPress clickjacking protection
Related: X-Frame-Options vs CSP frame-ancestors: the sharp edges most guides skip
Related: Why JavaScript frame-busting doesn't stop clickjacking
Related: DoubleClickjacking: the attack that bypasses framing protections