CSP frame-ancestors vs X-Frame-Options
Understanding X-Frame-Options
X-Frame-Options is an HTTP response header that allows you to control whether a browser should be allowed to render a page in a<frame>
, <iframe>
, <embed>
or <object>
. This helps avoid clickjacking attacks.
Available Values
- DENY: Prevents any domain from framing the content
- SAMEORIGIN: Allows framing only by pages on the same origin
- ALLOW-FROM origin (Deprecated): Allowed framing by specific domains (no longer supported by modern browsers)
Example of X-Frame-Options header
X-Frame-Options "DENY";
Implementation in different web servers:
Nginx
Add to your server block:Example of X-Frame-Options header on Nginx
add_header X-Frame-Options "DENY";
Apache
Add to your .htaccess or configuration:Example of X-Frame-Options header on Apache
Header set X-Frame-Options "DENY"
Next.js
Add to your next.config.js:Example of X-Frame-Options header in Next.js
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY'
}
]
}
]
}
}
CSP frame-ancestors Directive
The frame-ancestors directive is part of Content Security Policy and provides more granular control over who can embed your content. Learn more about Content Security PolicyDirective Values
- 'none': Prevents any embedding
- 'self': Allows embedding only from the same origin
- URI/Domain: Allows embedding from specific sources
Example of frame-ancestors directive
Content-Security-Policy: frame-ancestors 'self' https://trusted.com;
Meta Tag Limitation
The frame-ancestors directive is not supported in meta tags. It must be set via HTTP header.
Header Priority in Modern Browsers
When both X-Frame-Options and frame-ancestors are present, modern browsers will prioritize the frame-ancestors directive and ignore X-Frame-Options completely.Example of conflicting header policies
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors https://allowed-domain.com;
Browser Behavior
In this example, modern browsers will allow framing from allowed-domain.com, completely ignoring the DENY from X-Frame-Options.
• Modern Browsers :
• Legacy Browsers
Important Note
Due to this priority behavior, ensure your frame-ancestors directive is at least as restrictive as your X-Frame-Options header to maintain security across all browsers.
Comparing Both Approaches
X-Frame-Options
Simple to implement but limited in functionality
- Only supports one domain with ALLOW-FROM (deprecated)
- Limited browser support for some features
frame-ancestors
More flexible and powerful, with better browser support
- Supports multiple domains
- Widely supported in modern browsers
- Provides more granular control
Best Practices
Implementation Guidelines
Follow these best practices to ensure maximum security and compatibility across browsers.
- Use both headers for maximum compatibility
- Prefer frame-ancestors for modern browsers
- Use DENY by default unless embedding is required
- Regularly audit allowed domains
Example of using both headers for maximum protection & compatibility
Content-Security-Policy: frame-ancestors 'none';
X-Frame-Options: DENY
Recommendation
While using both headers provides the best coverage, frame-ancestors is the modern standard and should be your primary method of control.
Common Use Cases
- Public Content : Use 'none' to prevent any framing
- Internal Tools : Use 'self' to allow same-origin framing
- Partner Integration : Specify allowed partner domains
Conclusion
While X-Frame-Options provides basic protection against clickjacking, the CSP frame-ancestors directive offers more flexibility and better browser support. Using both headers ensures maximum compatibility across browsers while providing robust protection against framing-based attacks.
Continue Reading
How to setup nonce with NextJS
Learn how to properly setup nonce with NextJS to ensure a secure CSP configuration.

CSP & meta tags
Learn how to implement Content-Security-Policy using meta tags and understand the limitations compared to HTTP headers.
