Web Security

X-Frame-Options vs CSP frame-ancestors

Learn the differences between X-Frame-Options and CSP frame-ancestors directives, why both are recommended for clickjacking protection, and how to implement them correctly.

By Inventive HQ Team

X-Frame-Options and CSP frame-ancestors both stop clickjacking by controlling which sites are allowed to embed your page in a frame or iframe — but frame-ancestors is the modern replacement for the older, coarser X-Frame-Options. X-Frame-Options (2008) offers only two working values, DENY and SAMEORIGIN, and its third value ALLOW-FROM is deprecated and unsupported. CSP frame-ancestors (Content Security Policy Level 2) accepts a list of allowed origins and wildcards, and where both headers are present, any browser that supports CSP obeys frame-ancestors and ignores X-Frame-Options. The recommendation: use frame-ancestors as your real policy, and keep X-Frame-Options: SAMEORIGIN (or DENY) only as a fallback for very old browsers that never implemented CSP.

That is the summary. The rest of this article is the part an AI overview can't give you: the exact syntax and precedence rules, a side-by-side comparison, correct header examples you can paste, and the specific mistakes — deprecated ALLOW-FROM, meta-tag frame-ancestors, conflicting policies — that quietly leave a "protected" page fully frameable.

Clickjacking Protection: Two Headers, One Goal

Clickjacking attacks trick users into clicking invisible iframes overlaid on seemingly legitimate content, causing unintended actions like transferring money, changing settings, or authorizing access. Two HTTP headers protect against this threat: the older X-Frame-Options and the newer CSP frame-ancestors directive.

The diagram below shows the core mechanic — an attacker page tries to load your page in a hidden iframe, and the framing header refuses.

A framing header blocking a clickjacking attempt An attacker page tries to load your page inside a hidden iframe; the frame-ancestors header rejects the request and the iframe stays blank. How a framing header stops clickjacking attacker.example malicious page hidden <iframe> src="yoursite" yoursite.example your page frame-ancestors 'none' frame request The header rejects the cross-origin frame — the iframe stays blank, so there is nothing to click-hijack.

Understanding which to use—or why to use both—is essential for comprehensive clickjacking protection.

At a Glance: X-Frame-Options vs CSP frame-ancestors

X-Frame-OptionsCSP frame-ancestors
Values / syntaxDENY, SAMEORIGIN, ALLOW-FROM uri (deprecated)'none', 'self', host list, scheme/host wildcards
Multiple allowed originsNo — single value onlyYes — list any number of origins
WildcardsNoYes (e.g. https:, *.example.com)
DeliveryHTTP response header onlyHTTP response header only (ignored in <meta>)
Browser supportAll browsers, including IE8+All modern browsers since 2016 (no IE)
Precedence when both presentIgnored by CSP-aware browsersWins — obeyed, X-Frame-Options ignored
StatusLegacy / supersededCurrent standard
Example headerX-Frame-Options: SAMEORIGINContent-Security-Policy: frame-ancestors 'self' https://trusted.example

Which should you use? Lead with frame-ancestors; keep X-Frame-Options only as a fallback for very old browsers.

X-Frame-Options: The Legacy Standard

X-Frame-Options was introduced by Microsoft in 2008 and became an industry standard for preventing pages from being embedded in frames.

Advertisement

Syntax and Values

DENY: Never allow framing

X-Frame-Options: DENY

SAMEORIGIN: Allow framing only by same origin

X-Frame-Options: SAMEORIGIN

ALLOW-FROM uri: Allow specific origin (deprecated, poor browser support)

X-Frame-Options: ALLOW-FROM https://trusted.com

Advantages

Wide browser support: Works in all browsers including Internet Explorer 8+ Simple implementation: Single header with straightforward values Proven track record: Over 15 years of deployment experience

Limitations

Single value only: Cannot specify multiple allowed origins ALLOW-FROM deprecated: Modern browsers removed support No granular control: Binary allow/deny per origin Being superseded: CSP frame-ancestors is the modern replacement

CSP frame-ancestors: The Modern Approach

frame-ancestors is part of Content Security Policy Level 2, offering more flexible frame control:

Syntax

Content-Security-Policy: frame-ancestors 'none'
Content-Security-Policy: frame-ancestors 'self'
Content-Security-Policy: frame-ancestors 'self' https://trusted.com https://another-trusted.com

Values

'none': Equivalent to X-Frame-Options: DENY 'self': Equivalent to X-Frame-Options: SAMEORIGIN Multiple origins: List any number of allowed domains Wildcard schemes: Use https: to allow all HTTPS origins

Advantages

Multiple origins: Allow framing by several specific domains Granular control: Precise whitelisting of trusted framers CSP integration: Part of broader security policy Modern standard: Recommended by security experts

Browser Support

Supported by all modern browsers since 2016, but legacy browser support lags behind X-Frame-Options.

Which Should You Use?

Lead with CSP frame-ancestors, and keep X-Frame-Options only as a fallback. frame-ancestors is the current standard, more flexible, and where both headers are present it takes precedence — CSP-aware browsers obey it and ignore X-Frame-Options entirely. X-Frame-Options earns its place solely by covering browsers so old they never implemented CSP.

Dual Implementation (belt and braces)

Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Modern browsers use frame-ancestors for its flexibility and precedence; the rare legacy browser falls back to X-Frame-Options. This approach gives you:

Maximum compatibility: protection across old and new browsers Defense in depth: a redundant policy if one header is stripped by a proxy No real cost: both headers are tiny and set once at the server

When CSP frame-ancestors Alone Suffices

Every browser released since 2016 supports frame-ancestors, so if you have no meaningful legacy-browser (IE11, very old mobile) traffic, frame-ancestors on its own is complete protection — with greater flexibility than X-Frame-Options could ever offer. Only send both when you actually need the legacy fallback.

Implementation Examples

Complete Denial

Block all framing:

X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

Use for: Login pages, payment forms, administrative interfaces

Same-Origin Only

Allow framing only by your own site:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'

Use for: Applications where different pages frame each other

Multiple Trusted Origins

Allow specific external sites (CSP only):

Content-Security-Policy: frame-ancestors 'self' https://partner1.com https://partner2.com

X-Frame-Options cannot express this—use SAMEORIGIN as fallback:

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://partner1.com https://partner2.com

Older browsers get less permissive policy (SAMEORIGIN) while modern browsers allow listed partners.

Common Mistakes

Conflicting Headers

Problem: Different policies in X-Frame-Options and frame-ancestors

X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'none'

Result: Modern browsers use frame-ancestors ('none'), old browsers use X-Frame-Options (SAMEORIGIN), creating inconsistent protection

Solution: Make policies equivalent or intentionally more restrictive in X-Frame-Options

Using Deprecated ALLOW-FROM

Problem:

X-Frame-Options: ALLOW-FROM https://trusted.com

Result: Ignored by most modern browsers

Solution: Use CSP frame-ancestors for multiple origins, X-Frame-Options: SAMEORIGIN for legacy fallback

Missing from All Responses

Problem: Headers only on some responses, not all

Result: Unprotected pages can be framed

Solution: Set headers at web server level for all responses

Testing Frame Protection

Verify implementation:

Browser Developer Tools: Check Response Headers in Network tab

Manual Test: Attempt to embed page in iframe:

<iframe src="https://yoursite.com"></iframe>

Should result in console error: "Refused to display in a frame because it set 'X-Frame-Options' to 'deny'"

Security Scanners: Use Security Headers Analyzer

Exceptions: When to Allow Framing

Some legitimate use cases require framing:

Embedded widgets: Maps, videos, social media embeds Partner integrations: Trusted third parties embedding your content Internal applications: Dashboards framing various internal tools

For these scenarios:

  • Use CSP frame-ancestors with specific allowed origins
  • Never use 'unsafe-inline' or overly broad whitelists
  • Regularly audit which origins are allowed
  • Consider alternative integration methods (APIs, redirects)

Conclusion

Both X-Frame-Options and CSP frame-ancestors prevent clickjacking by controlling where your pages can be framed. X-Frame-Options offers broader legacy browser support but limited flexibility. CSP frame-ancestors provides granular control with multiple allowed origins but doesn't support very old browsers.

The best practice is to lead with CSP frame-ancestors and keep X-Frame-Options as an equivalent (or more restrictive) fallback. Modern browsers use frame-ancestors for its flexibility and precedence, while the rare legacy browser falls back to X-Frame-Options. This ensures comprehensive protection across all browsers without relying on a header that has been superseded.

For maximum security on sensitive pages (login, payments, admin), use DENY/'none' to block all framing. For less sensitive content needing selective framing, use CSP frame-ancestors with explicitly whitelisted trusted origins and X-Frame-Options: SAMEORIGIN as fallback.

Validate your frame protection implementation with our Security Headers Analyzer to ensure proper configuration and comprehensive clickjacking defense.

Frequently Asked Questions

What is the difference between X-Frame-Options and CSP frame-ancestors?

Both HTTP response headers stop clickjacking by controlling which sites are allowed to embed your page in a frame or iframe. X-Frame-Options is the older, coarser header from 2008 with only two working values — DENY and SAMEORIGIN. CSP frame-ancestors is the modern replacement, part of Content Security Policy Level 2: it accepts a list of allowed origins, supports wildcards, and where both headers are present, browsers that support CSP obey frame-ancestors and ignore X-Frame-Options. The recommendation is to use frame-ancestors as your real policy and keep X-Frame-Options only as a fallback for very old browsers.

Do I need both X-Frame-Options and frame-ancestors?

Strictly, no. Every browser released since 2016 supports CSP frame-ancestors, so it alone gives complete clickjacking protection. You add X-Frame-Options purely as a belt-and-braces fallback for very old browsers (such as Internet Explorer) that never implemented frame-ancestors. If you have no meaningful legacy-browser traffic, frame-ancestors on its own is enough. Sending both costs almost nothing, so many teams keep X-Frame-Options: SAMEORIGIN or DENY alongside it as defense in depth.

Which header wins if X-Frame-Options and frame-ancestors conflict?

CSP frame-ancestors wins. The Content Security Policy specification states that if a response contains frame-ancestors, the browser must ignore X-Frame-Options entirely. So if you send X-Frame-Options: SAMEORIGIN but frame-ancestors 'none', a modern browser blocks all framing (obeying 'none'). Only browsers that do not understand CSP frame-ancestors will fall back to reading X-Frame-Options. To avoid confusion, keep the two headers logically equivalent, or make X-Frame-Options the more restrictive of the two.

Is X-Frame-Options ALLOW-FROM still supported?

No. X-Frame-Options: ALLOW-FROM https://example.com is deprecated and effectively unsupported — Chrome and modern Chromium browsers never implemented it, and Firefox removed it. Most browsers silently ignore an ALLOW-FROM value, which can leave a page you believed was protected fully frameable. To allow a specific external origin to frame your page, use CSP frame-ancestors with that origin listed instead; ALLOW-FROM has no reliable equivalent behavior.

How do I allow only specific origins to frame my page?

Use CSP frame-ancestors and list each allowed origin: Content-Security-Policy: frame-ancestors 'self' https://partner1.example https://partner2.example. X-Frame-Options cannot express a multi-origin allow-list — its only options are DENY (no one) and SAMEORIGIN (your own origin), and the ALLOW-FROM value that once did this is deprecated. So a per-origin allow-list is a frame-ancestors-only capability. As a legacy fallback you can add X-Frame-Options: SAMEORIGIN, which is more restrictive but safe.

Does CSP frame-ancestors work in a meta tag?

No. frame-ancestors is one of a small set of CSP directives that only work when delivered as a real HTTP response header. It is explicitly ignored when Content Security Policy is set via an HTML meta http-equiv tag, because by the time the HTML is parsed the framing decision has already been made. You must set frame-ancestors at the web server, application, or CDN level as a response header. X-Frame-Options likewise only works as an HTTP header, not a meta tag.

What is the frame-ancestors equivalent of X-Frame-Options: DENY and SAMEORIGIN?

frame-ancestors 'none' is equivalent to X-Frame-Options: DENY — no site, not even your own, may frame the page. frame-ancestors 'self' is equivalent to X-Frame-Options: SAMEORIGIN — only pages on your own origin may frame it. Beyond those two, frame-ancestors goes further than X-Frame-Options can: you can list specific origins, and you can use scheme or host wildcards, none of which X-Frame-Options supports.

How do I test whether my clickjacking protection works?

Load your page inside an iframe on a different origin and watch the browser console. If protection is working you will see a refusal such as "Refused to display ... in a frame because it set 'X-Frame-Options' to 'deny'" or a CSP frame-ancestors violation, and the frame stays blank. You can also inspect the response headers in your browser's Network tab, or run the page through a scanner such as the Security Headers Analyzer to confirm the headers are present on every response, not just the homepage.

X-Frame-OptionsCSPframe-ancestorsclickjackingweb security