What Is X-Frame-Options?
X-Frame-Options is an HTTP security header that controls whether a browser is allowed to render your web page inside an <iframe>, <frame>, <embed>, or <object> element. By setting this header, you tell the browser whether other websites are permitted to embed your content within their pages. The primary purpose of X-Frame-Options is to prevent clickjacking attacks, where an attacker overlays a transparent iframe of your site on top of a malicious page and tricks users into clicking on hidden elements.
Clickjacking is a deceptively simple but effective attack. An attacker creates a page with an enticing button or link, positions a transparent iframe of your site directly over that button, and when the user clicks what they think is a harmless element, they are actually clicking a button on your site. This could trigger actions like changing account settings, making a purchase, or granting permissions — all without the user's knowledge. X-Frame-Options eliminates this risk by preventing your page from being framed entirely or restricting framing to trusted origins.
X-Frame-Options Directives
DENY
The DENY directive is the most restrictive option. It prevents your page from being displayed in a frame under any circumstances, even if the request comes from the same domain. This is the safest choice for pages that should never appear inside an iframe, such as login pages, administrative panels, and payment forms.
X-Frame-Options: DENY
With DENY set, even your own site cannot embed the page in an iframe. If you have legitimate use cases for embedding your own content (such as a preview panel in a CMS), you should use SAMEORIGIN instead.
SAMEORIGIN
The SAMEORIGIN directive allows the page to be framed only by pages from the same origin. Origin matching includes the scheme (HTTP or HTTPS), hostname, and port number. This is the most commonly used directive because it permits your own site to use iframes internally while blocking external sites from embedding your content.
X-Frame-Options: SAMEORIGIN
SAMEORIGIN is ideal for most web applications. It protects against clickjacking from external attackers while allowing legitimate internal framing. For example, a web application that uses iframes to display help content within the same domain can safely use SAMEORIGIN.
ALLOW-FROM (Deprecated)
The ALLOW-FROM directive was designed to allow framing from a specific origin. For example:
X-Frame-Options: ALLOW-FROM https://trusted-partner.com
However, ALLOW-FROM has been deprecated and is no longer supported by modern browsers, including Chrome, Firefox (since version 70), and Safari. Browsers that do not recognize ALLOW-FROM will silently ignore the entire X-Frame-Options header, which means relying on ALLOW-FROM can leave your site with no clickjacking protection at all. If you need to allow framing from specific external origins, you should use the Content Security Policy frame-ancestors directive instead.
X-Frame-Options vs. CSP frame-ancestors
The Content Security Policy (CSP) frame-ancestors directive is the modern replacement for X-Frame-Options. While X-Frame-Options supports only three values (DENY, SAMEORIGIN, and the deprecated ALLOW-FROM), frame-ancestors offers much more flexibility:
# Block all framing (equivalent to X-Frame-Options: DENY)
Content-Security-Policy: frame-ancestors 'none';
# Allow same-origin framing (equivalent to X-Frame-Options: SAMEORIGIN)
Content-Security-Policy: frame-ancestors 'self';
# Allow framing from specific origins (no X-Frame-Options equivalent in modern browsers)
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com https://another-site.com;
Key differences between the two approaches:
- Multiple origins:
frame-ancestorssupports multiple allowed origins in a single directive, while ALLOW-FROM only allowed one (and is now deprecated). - Wildcard support:
frame-ancestorssupports wildcards for subdomains, such ashttps://*.example.com. - Precedence: When both X-Frame-Options and CSP frame-ancestors are present, the CSP directive takes precedence in browsers that support both. However, older browsers that do not support CSP will still use X-Frame-Options.
- Browser support: CSP frame-ancestors is supported by all modern browsers, but setting both headers provides defense in depth for legacy browser users.
Best Practice: Set Both Headers
For maximum compatibility, the recommended approach is to set both X-Frame-Options and CSP frame-ancestors. This ensures that modern browsers respect the CSP directive while legacy browsers fall back to X-Frame-Options:
# Apache (.htaccess or virtual host)
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"
# Nginx (server block)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
Make sure the two headers are consistent. If X-Frame-Options is set to DENY but frame-ancestors allows specific origins, the behavior will differ across browsers depending on which header they prioritize.
Server Configuration Examples
Apache
To set X-Frame-Options in Apache, you can add the header in your .htaccess file, virtual host configuration, or server configuration. The mod_headers module must be enabled:
# Enable mod_headers if not already loaded
# a2enmod headers
# In .htaccess or virtual host config
Header always set X-Frame-Options "DENY"
# Or for SAMEORIGIN
Header always set X-Frame-Options "SAMEORIGIN"
Using always ensures the header is set on all responses, including error pages (such as 404 or 500 responses), which could also be targeted by clickjacking.
Nginx
In Nginx, add the header in your server block or location block:
server {
# Apply to the entire server block
add_header X-Frame-Options "SAMEORIGIN" always;
# Or apply to specific locations
location /admin/ {
add_header X-Frame-Options "DENY" always;
}
}
The always parameter in Nginx ensures the header is included in all response codes, not just successful ones.
Application-Level Headers
Many web frameworks allow setting headers programmatically. This can be useful when different pages require different framing policies:
# PHP
header('X-Frame-Options: SAMEORIGIN');
# Node.js / Express
res.setHeader('X-Frame-Options', 'DENY');
# Python / Django (middleware or view)
response['X-Frame-Options'] = 'DENY'
Testing X-Frame-Options
You can verify that the X-Frame-Options header is being sent correctly using command-line tools:
# Using curl
curl -I https://example.com | grep -i x-frame-options
# Expected output
X-Frame-Options: SAMEORIGIN
To test the actual behavior, create a simple HTML page that attempts to iframe your site. If X-Frame-Options is working correctly, the browser will refuse to load the frame and display an error in the developer console:
<!-- Test page: save as test.html and open in browser -->
<html>
<body>
<h1>Clickjacking Test</h1>
<iframe src="https://your-site.com" width="800" height="600"></iframe>
</body>
</html>
If protection is working, the iframe will be empty and the browser console will show a message like: "Refused to display 'https://your-site.com/' in a frame because it set 'X-Frame-Options' to 'DENY'."
Common Mistakes
- Relying on ALLOW-FROM: Since ALLOW-FROM is not supported by modern browsers, any site using it as its only framing control effectively has no clickjacking protection. Migrate to CSP frame-ancestors for per-origin allow lists.
- Missing the header on error pages: If your custom 404 or 500 pages do not include X-Frame-Options, an attacker could potentially use those pages in a clickjacking scenario. Use the
alwayskeyword in Apache and Nginx to cover all response codes. - Setting the header only on HTML pages: While clickjacking primarily targets HTML pages, setting the header on all content types provides broader protection and avoids edge cases.
- Inconsistent settings across environments: Ensure that your staging, development, and production environments all have consistent X-Frame-Options settings. A misconfigured staging environment exposed to the internet without the header could be exploited.
- Forgetting about embedded content: If your site legitimately needs to be embedded (for widgets, payment forms, or social media embeds), using DENY will break that functionality. Evaluate your framing requirements before choosing a directive.
When to Use Which Directive
Choosing the right directive depends on your application requirements:
- DENY: Use for pages that should never be framed — login pages, admin panels, settings pages, and any page that performs sensitive actions.
- SAMEORIGIN: Use as a default for most web applications. It allows your own site to use iframes internally (for modal dialogs, embedded help, or internal tools) while blocking external framing.
- CSP frame-ancestors with specific origins: Use when you need to allow trusted third parties to embed your content. This is common for payment processors, social media widgets, and partner integrations.
Summary
X-Frame-Options is a straightforward but effective security header that prevents clickjacking attacks by controlling whether your pages can be embedded in iframes. Set it to DENY or SAMEORIGIN depending on your needs, and pair it with the CSP frame-ancestors directive for comprehensive protection across all browsers. Combined with other security headers like HSTS and CSP, X-Frame-Options is an essential part of a secure web application. For guidance on deploying all security headers together, see our security header configuration guide.
Need help securing your web applications? Explore NOC.org plans to get started.