Reference guide · website-errors · Published 2026-08-16 · 3 min read
X-Frame-Options vs CSP frame-ancestors
X-Frame-Options vs CSP frame-ancestors, why frame-ancestors wins when both are set, and how to send both headers safely.
- ·What each does
- ·Precedence when both
- ·Set both safely
Both headers stop a page being framed by another site, which is the usual defence against clickjacking, but they are not equivalent. X-Frame-Options is the older, simpler header with only a few values. Content-Security-Policy: frame-ancestors is the newer directive that expresses the same intent more precisely and overrides the older header in modern browsers. Sending both, with the CSP directive carrying the real policy and the older header as a fallback, covers every browser.
What each does
X-Frame-Options (RFC 7034) takes one of a fixed set of values:
| Value | Meaning |
|---|---|
DENY | No other page may frame this one |
SAMEORIGIN | Only the same origin may frame it |
ALLOW-FROM <uri> | Permits one origin (not widely supported) |
frame-ancestors is a CSP Level 2 directive that names which origins may frame the resource:
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com
It supports 'self', 'none', and a list of origins, so it can allow several parent sites, which the older header cannot express. Note that frame-ancestors does not apply inside a meta tag; it must be sent as an HTTP header.
Precedence when both are present
Per the CSP specification, when a response carries both, frame-ancestors takes precedence and the browser ignores X-Frame-Options. This is implemented by current engines:
- A page with
X-Frame-Options: DENYplusframe-ancestors 'self'is framable on its own origin, because the CSP directive is the effective policy. - The opposite ordering also holds: the CSP value wins even if
X-Frame-Optionsis stricter.
This is not a bug; it is the defined contract. If you deploy both and they disagree, the CSP frame-ancestors value is what modern browsers enforce, so make them agree or accept the CSP as authoritative.
Set both safely
Because the CSP value wins in modern browsers and the older header only matters for legacy clients, set them to be consistent:
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'
- Use
SAMEORIGIN(notDENY) in the old header when your CSP allows'self', so the two agree. - Keep the old header only as a fallback for agents that do not implement CSP Level 2.
- If you need to allowlist several external origins, the CSP directive is the only way, and the CSP value is the one that takes effect in current browsers. The WordPress security headers article shows where this header block fits in a CMS deployment.
Troubleshooting framing failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Page renders in Chrome but not Safari | Leftover DENY plus a permissive CSP | Make the CSP the policy; align the old header |
| Page that framed before now blocked | A newly added CSP policy overrode an older lenient header | Confirm the CSP is the intended frame policy |
| Iframe blank but no error | The embedded page sends frame-ancestors 'none' | Relax the directive to the embedding origin |
| Headers missing entirely | The server or CDN strips them | Add them at the web server or CDN layer |
A page only appears to fail if the two headers conflict. The mixed content guide covers a related framing symptom, and too many redirects is a different tab error that is often mistaken for a framing problem when an iframe target redirects.