# X-Frame-Options vs frame-ancestors, which clickjacking control to use (/en/blog/x-frame-options-vs-frame-ancestors)



Both answer the same question: who is allowed to put your page inside a frame,
which is the control that stops clickjacking. The [`frame-ancestors`](/en/docs/web-security/policies/content-security-policy/directives/frame-ancestors)
directive in [Content Security Policy (CSP)](/en/docs/web-security/policies/content-security-policy) is the modern, more expressive one;
[`X-Frame-Options`](/en/docs/web-security/security-headers/x-frame-options)
is the older header kept for clients that never implemented CSP. Send both. They
cannot conflict: any browser that enforces `frame-ancestors` is required by the
CSP spec to ignore `X-Frame-Options`, so the CSP directive decides on modern
browsers and the older header covers the rest.

## The clickjacking threat [#the-clickjacking-threat]

Clickjacking is an attack where the attacker loads your page inside a frame on a
site they control, usually invisible or transparent, and lays their own bait over
it. The victim thinks they are clicking a button on the attacker's page, but the
click lands on your framed page: confirm a payment, change a setting, approve a
permission. The defense is to tell the browser which sites, if any, may frame your
page. Both controls below do exactly that.

## X-Frame-Options, the original header [#x-frame-options-the-original-header]

`X-Frame-Options` is the original anti-clickjacking header, documented in RFC
7034\. It has two values that still work.

* `DENY`: no site may frame the page. MDN's wording is that the page "cannot be
  loaded in any frame, regardless of origin", so not even your own site can
  frame it.
* `SAMEORIGIN`: only pages from the same origin may frame it.

```http
X-Frame-Options: DENY
```

One nuance on `SAMEORIGIN`. Modern browsers check every ancestor in the frame
chain, so a same-origin frame nested inside a hostile page is still blocked. RFC
7034 documented that early implementations checked only the top-level browsing
context, which left that nesting trick open; current engines close it.

Despite its age, the header is not deprecated. In January 2025 the HTML spec
editors deliberately removed the words "legacy" and "obsoleted by" from its
description of the header, and the CSP spec now says `frame-ancestors`
"overrides" `X-Frame-Options` rather than "obsoletes" it. Every browser still
enforces it, and there are no plans to remove it.

### ALLOW-FROM is obsolete, and it fails open [#allow-from-is-obsolete-and-it-fails-open]

The header once had a third value, `ALLOW-FROM uri`, meant to permit a single
named origin. It started as an Internet Explorer extension, was never uniformly
implemented (RFC 7034's own words), could only ever name one origin with no
wildcards, and Firefox, which had also shipped it, removed it in 2019 and
pointed at `frame-ancestors` as the replacement.

The dangerous part is how it fails. A browser that does not recognize
`ALLOW-FROM` treats the value as invalid and drops the entire header. Chromium's
console message spells it out: "'ALLOW-FROM' is not a recognized directive. The
header will be ignored." So a site that ships only
`X-Frame-Options: ALLOW-FROM https://partner.example.com` today has no framing
restriction at all in modern browsers, which is failing open; OWASP warns that a
site depending on `ALLOW-FROM` has no clickjacking defense in browsers that do
not support it. If you need to allow specific framers, that is a job for
`frame-ancestors`.

### Conflicting values fall back to deny [#conflicting-values-fall-back-to-deny]

A related gotcha for layered infrastructure. If two layers set different values,
say the application sends `SAMEORIGIN` and a CDN adds `DENY`, the browser
receives conflicting `X-Frame-Options` values. Chromium treats that as an error
and falls back to `deny`, refusing to display the page in any frame. When
same-origin framing that used to work breaks after an infrastructure change,
duplicated headers are worth checking first.

## frame-ancestors, the CSP replacement [#frame-ancestors-the-csp-replacement]

`frame-ancestors` arrived in CSP Level 2 and is the directive that takes over
the job. Instead of fixed keywords, it takes a source list: `'none'`, `'self'`,
and any number of host or scheme sources, including wildcard subdomains like
`*.example.com`. That is what makes it more expressive. You can allow several
trusted partners at once, an entire subdomain tree, or a specific scheme, none
of which `X-Frame-Options` can express. Nonces and hashes do not apply to it,
since it lists framing parents, not scripts. The browser checks every ancestor
in the frame chain, and if any one of them fails the list, the load is
cancelled.

```http
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com
```

Two deployment rules matter more than anything else about it.

First, it has no fallback: `default-src` does not cover it. MDN is explicit that
"a policy that declares `default-src 'none'` still allows the resource to be
embedded by anyone". A locked-down CSP without an explicit `frame-ancestors`
line says nothing about framing, so set it explicitly.

Second, it is header-only. The CSP spec excludes `frame-ancestors` (along with
`report-uri` and `sandbox`) from `<meta http-equiv="Content-Security-Policy">`
delivery, so browsers ignore it in a meta tag. Set it at the server or CDN. If
you want the clickjacking protection without adopting a full policy, see
[how to set frame-ancestors without a full CSP header](/en/blog/frame-ancestors-without-csp-header).

Support is not a real constraint anymore: every modern engine (Blink, Gecko,
WebKit) enforces `frame-ancestors`. The one notable gap is Internet Explorer,
which never implemented it and only understands `X-Frame-Options`.

## Precedence, the exact rule [#precedence-the-exact-rule]

The CSP spec settles what happens when both headers arrive on the same
response: "If a resource is delivered with a policy that includes a directive
named frame-ancestors and whose disposition is 'enforce', then the
X-Frame-Options header MUST be ignored." Chromium implements that literally; its
ancestor throttle lets `frame-ancestors` decide instead of `X-Frame-Options`
whenever the response carries an enforced `frame-ancestors` directive.

Note the word "enforce". Only an enforced `frame-ancestors` switches
`X-Frame-Options` off. If you are trialing the directive in a
[`Content-Security-Policy-Report-Only`](/en/docs/web-security/policies/content-security-policy/report-only)
header, it reports violations but does not
suppress `X-Frame-Options`, so the older header keeps enforcing while you watch
the reports. That is exactly the behavior you want during a rollout.

This precedence rule is why sending both is safe. There is no state where the
two fight each other: browsers that understand `frame-ancestors` use it and
drop `X-Frame-Options`, and older clients that predate CSP Level 2 read
`X-Frame-Options` alone. OWASP's clickjacking guidance recommends stacking
independent defenses for the same reason.

## X-Frame-Options vs frame-ancestors side by side [#x-frame-options-vs-frame-ancestors-side-by-side]

| Capability                      | X-Frame-Options                                                          | frame-ancestors                                     |
| ------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------- |
| Allow no framing at all         | `DENY`                                                                   | `'none'`                                            |
| Allow same-origin framing only  | `SAMEORIGIN`                                                             | `'self'`                                            |
| Allow specific external origins | Not possible (`ALLOW-FROM` obsolete, fails open)                         | `'self' https://partner.example.com`                |
| Wildcard subdomains             | Not possible                                                             | `*.example.com`                                     |
| Ancestors checked               | Whole chain in modern browsers (top-level only in early implementations) | Whole chain, any failing ancestor cancels the load  |
| Set via meta tag                | No effect (header only)                                                  | No effect (header only)                             |
| Covered by `default-src`        | Not applicable                                                           | No, must be set explicitly                          |
| When both are sent              | Ignored where enforced `frame-ancestors` is present                      | Takes precedence when enforced                      |
| Support                         | All engines, including old IE                                            | All modern engines (Blink, Gecko, WebKit); never IE |

## Recommendation [#recommendation]

Ship both, with the strictest setting your site allows. Use `'none'` and `DENY`
if nothing should ever frame your page; use `'self'` and `SAMEORIGIN` if your
own app frames itself.

```http
Content-Security-Policy: frame-ancestors 'none'
```

```http
X-Frame-Options: DENY
```

Never use `ALLOW-FROM`, and remove it where you find it, since it can leave a
page with no protection at all. If you need to allow named partners, express
that in `frame-ancestors` only and keep `X-Frame-Options` at `SAMEORIGIN` or
`DENY` for the old clients that cannot read the allowlist anyway. You can
confirm both headers are present and correctly formed with CentralCSP's free
[security headers scanner](/tools/security-headers).

The two headers also differ after deployment, not just in syntax: `frame-ancestors`
violations are reported through the Reporting API, and `X-Frame-Options` refusals are
not reported at all. Pointing the CSP at a CentralCSP endpoint means a partner site
that suddenly cannot embed you shows up as a csp-violation naming the framing origin,
rather than as a support ticket.

## FAQ [#faq]

### Is X-Frame-Options deprecated? [#is-x-frame-options-deprecated]

No. Neither MDN nor the browser compatibility data marks it deprecated, and
every browser still enforces it. In January 2025 the HTML spec editors removed
the words "legacy" and "obsoleted by" from its description, and the CSP spec now
says `frame-ancestors` "overrides" it rather than "obsoletes" it. Only the
`ALLOW-FROM` value is obsolete. Keep sending the header alongside
`frame-ancestors`.

### Should I use both X-Frame-Options and frame-ancestors? [#should-i-use-both-x-frame-options-and-frame-ancestors]

Yes. They cannot conflict, because the CSP spec requires any browser enforcing
`frame-ancestors` to ignore `X-Frame-Options`. So the CSP directive decides on
every modern browser, and `X-Frame-Options` covers old clients such as Internet
Explorer that never implemented CSP. OWASP recommends layering independent
clickjacking defenses, and sending both headers costs one line each.

### Why is X-Frame-Options ALLOW-FROM not working? [#why-is-x-frame-options-allow-from-not-working]

Because it is obsolete. It began as an Internet Explorer extension, was never
uniformly implemented elsewhere, and Firefox removed it in 2019. Worse, a
browser that does not recognize `ALLOW-FROM` ignores the whole header, so the
page fails open with no framing protection. Replace it with
`Content-Security-Policy: frame-ancestors 'self' https://partner.example.com`.

### Can I set frame-ancestors in a meta tag? [#can-i-set-frame-ancestors-in-a-meta-tag]

No. Browsers ignore `frame-ancestors` in a meta tag, and a meta tag does not
work for `X-Frame-Options` either, so set both at the server or CDN. For the
details and the deployment options, see
[how to set frame-ancestors without a full CSP header](/en/blog/frame-ancestors-without-csp-header).

### Does frame-ancestors in Report-Only mode disable X-Frame-Options? [#does-frame-ancestors-in-report-only-mode-disable-x-frame-options]

No. The precedence rule applies only to a policy "whose disposition is
'enforce'", so a `frame-ancestors` directive in a
`Content-Security-Policy-Report-Only` header reports violations while
`X-Frame-Options` keeps enforcing. That makes Report-Only a safe way to test an
allowlist before switching it on.

## Related reading [#related-reading]

* [frame-ancestors](/en/docs/web-security/policies/content-security-policy/directives/frame-ancestors)
  and [X-Frame-Options](/en/docs/web-security/security-headers/x-frame-options),
  the reference pages.
* [How to set frame-ancestors without a full CSP header](/en/blog/frame-ancestors-without-csp-header),
  when you want the clickjacking protection but not the rest of a policy yet.
* [Which legacy headers to retire](/en/blog/legacy-security-headers-to-retire),
  where `X-Frame-Options` sits among the other headers browsers have moved past.
* [Improve your security headers grade](/en/blog/improve-security-headers-grade),
  the full checklist this pair of headers belongs to.

## Sources [#sources]

* [CSP Level 3, relation of frame-ancestors to X-Frame-Options](https://w3c.github.io/webappsec-csp/#frame-ancestors-and-frame-options)
* [CSP Level 3, the frame-ancestors directive](https://www.w3.org/TR/CSP3/#directive-frame-ancestors)
* [CSP Level 3, meta element delivery exclusions](https://www.w3.org/TR/CSP3/#meta-element)
* [RFC 7034, HTTP Header Field X-Frame-Options](https://datatracker.ietf.org/doc/html/rfc7034)
* [MDN, X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options)
* [MDN, CSP frame-ancestors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/frame-ancestors)
* [MDN, Firefox release notes (ALLOW-FROM removal)](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/70)
* [OWASP, Clickjacking Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html)
* [Chromium, ancestor\_throttle.cc (X-Frame-Options and frame-ancestors handling)](https://github.com/chromium/chromium/blob/main/content/browser/renderer_host/ancestor_throttle.cc)
* [whatwg/html PR 10938 and w3c/webappsec-csp PR 702, the 2025 wording changes](https://github.com/whatwg/html/pull/10938)
