Guardians of the Enterprise — Insights from leading cyber experts.

Listen Now →
Supply Chain Attacks

Understanding X-Frame-Options: Examples and Benefits

6 min read Updated Supply Chain Attacks

What is X-Frame-Options?

X-Frame-Options is an HTTP response header that tells the browser whether a page can load inside a <frame>, <iframe>, <embed>, or <object>. It has two working values: DENY blocks all framing, and SAMEORIGIN allows framing only by pages from the same origin. Its main job is to stop clickjacking.

The header was standardized in RFC 7034 in 2013. Every major browser still enforces it. Today it works alongside the Content Security Policy (CSP) frame-ancestors directive, which is the modern replacement for most use cases.

A typical response looks like this:

HTTP/1.1 200 OK
Content-Type: text/html
X-Frame-Options: SAMEORIGIN

The server sends the header. The browser enforces it. If a page is framed against the rule, the browser shows a blank frame and logs an error in the console.

How X-Frame-Options Work?

The header takes one value per response. Browsers recognize DENY and SAMEORIGIN. ALLOW-FROM is obsolete and ignored by current browsers.

Value What it allows Browser support Use it?
DENY No framing at all, including by your own site All browsers Yes, for pages that never need to be framed
SAMEORIGIN Framing only by pages on the same origin (scheme, host, and port) All browsers Yes, when your own app frames its pages
ALLOW-FROM uri Framing by one named origin Not supported in Chrome, Edge, Safari, or current Firefox No. Use CSP frame-ancestors

DENY

X-Frame-Options: DENY stops the page from loading in any frame, from any origin. This includes frames on your own domain. Use it for login pages, account settings, payment steps, and admin panels.

X-Frame-Options: DENY

SAMEORIGIN

X-Frame-Options: SAMEORIGIN lets the page load in a frame only when the framing page has the same origin. Origin means the same scheme, host, and port. https://app.example.com and https://www.example.com are different origins.

X-Frame-Options: SAMEORIGIN

Modern browsers check every ancestor in the frame chain. If any parent frame comes from a different origin, the browser blocks the page.

ALLOW-FROM (obsolete)

ALLOW-FROM was meant to allow one trusted origin.

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

Chrome and Safari never supported it. Firefox removed support in version 70. A browser that does not understand ALLOW-FROM ignores the whole header. The page then has no framing protection at all. Use Content-Security-Policy: frame-ancestors instead (covered below).

You may also see ALLOWALL in old configs. It is not a standard value. It turns protection off. If a page must be embeddable, remove the header and control framing with frame-ancestors.

X-Frame-Options DENY vs SAMEORIGIN: Which Should You Use?

Use DENY by default. Switch to SAMEORIGIN only for pages your own application loads in a frame.

Scenario Recommended value
Login, signup, password reset DENY
Checkout and payment pages DENY
Admin panels and account settings DENY
Dashboards that embed your own reports or widgets SAMEORIGIN
CMS editors with live preview frames (for example, WordPress admin) SAMEORIGIN
Pages partners or customers must embed on their sites Neither. Use CSP frame-ancestors with an allowlist

A simple test helps. Ask whether any page on your own origin frames this page. If no, use DENY. If yes, use SAMEORIGIN. If another domain needs to frame it, X-Frame-Options cannot express that rule.

Is X-Frame-Options Deprecated?

No. The header itself is still supported and enforced by every major browser. Only the ALLOW-FROM value is obsolete. CSP frame-ancestors is now the preferred control, and the OWASP Clickjacking Defense Cheat Sheet recommends sending both.

X-Frame-Options vs CSP frame-ancestors

  X-Frame-Options CSP frame-ancestors
Block all framing DENY frame-ancestors 'none'
Allow same origin only SAMEORIGIN frame-ancestors 'self'
Allow specific external domains Not possible frame-ancestors 'self' https://partner.com
Allow multiple domains Not possible Yes, space-separated list
Wildcard subdomains No Yes, https://*.example.com
Works in a <meta> tag No No
Precedence when both are set Ignored by modern browsers Wins

When a response carries both headers, browsers that support frame-ancestors apply it and ignore X-Frame-Options. Older browsers fall back to X-Frame-Options. Sending both gives full coverage. Keep the two policies consistent so behavior does not change by browser.

How to allow multiple domains

X-Frame-Options allows one value and no domain list. To allow framing from several sites, use CSP:

Content-Security-Policy: frame-ancestors 'self' https://partner-one.com https://partner-two.com

Pair it with X-Frame-Options: SAMEORIGIN for legacy browsers. Modern browsers follow the CSP list. Legacy browsers fall back to same-origin only.

What Threats Does X-Frame-Options Prevent?

X-Frame-Options prevents attacks that depend on loading your page inside an attacker-controlled frame. The main one is clickjacking.

Clickjacking – The attacker loads your page in a transparent iframe and places a decoy button on top. The user thinks they are clicking the decoy. They actually click a button on your page while logged in. This can change account settings, approve a payment, grant OAuth access, or enable a camera permission. With DENY or SAMEORIGIN, the browser refuses to render your page in the attacker’s frame, so there is nothing to click.

UI redress variants – These include cursorjacking, likejacking, and drag-and-drop attacks that steal text or tokens from a framed page. Each needs the target page inside a frame. The header removes that precondition.

Frame-based phishing – An attacker can wrap your real login page inside a lookalike site and surround it with fake prompts. Blocking framing forces users to see your page in their own browser tab, with your real URL in the address bar.

X-Frame-Options does not stop cross-site scripting, CSRF, or a phishing site that copies your page HTML. It only controls framing. Treat it as one layer in a set of security headers.

How to Set the X-Frame-Options Header

Set the header at the web server or in the application framework. Use one place only, so the header is not sent twice. Each example below uses SAMEORIGIN. Replace it with DENY where that fits.

Apache

Enable mod_headers, then add this to the virtual host or .htaccess:

Header always set X-Frame-Options "SAMEORIGIN"

The always keyword adds the header to error responses as well.

Nginx

Add this to the server block:

add_header X-Frame-Options "SAMEORIGIN" always;

Nginx does not inherit add_header into a location block that defines its own add_header. Repeat security headers in those blocks, or use a shared include file.

IIS

Add this to web.config:

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="X-Frame-Options" value="SAMEORIGIN" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

Node.js (Express with Helmet)

const helmet = require("helmet");
app.use(helmet.frameguard({ action: "sameorigin" }));

Django

Django includes XFrameOptionsMiddleware and sends DENY by default. To change it, set this in settings.py:

X_FRAME_OPTIONS = "SAMEORIGIN"

Spring Security

Spring Security sends DENY by default. To allow same-origin framing:

http.headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()));

Adding CSP frame-ancestors

Send frame-ancestors next to X-Frame-Options with the same intent. Apache example:

Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"

If you already send a CSP header, add frame-ancestors to that policy. Two separate CSP headers are both enforced, which can block more than you expect.

Neither header works in an HTML <meta http-equiv> tag. Browsers ignore both there. They must be sent as HTTP response headers.

How to Test X-Frame-Options

Check the header three ways: from the command line, in the browser, and with a test frame.

  1. Command line – Run curl -sI https://www.example.com | grep -i x-frame-options. The output should show one line with the value you expect.
  2. Browser DevTools – Open the Network tab, reload the page, select the main document, and look under Response Headers. Check for duplicate X-Frame-Options lines and for a frame-ancestors directive in the CSP header.
  3. Test frame – Save the snippet below as an HTML file on a different origin and open it. A protected page shows a blank frame and a console error.
<iframe src="https://www.example.com/login" width="800" height="600"></iframe>

Test error pages, redirects, and API responses that return HTML. Those responses often skip headers that the main pages carry.

Fixing “Refused to Display in a Frame” Errors

This console error means the framed page sent X-Frame-Options or frame-ancestors, and your page is not allowed. Chrome shows it like this:

Refused to display 'https://www.example.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

The fix depends on who owns the framed page.

You own the framed page. Decide which sites need to embed it. For your own origin, use SAMEORIGIN or frame-ancestors 'self'. For external sites, remove X-Frame-Options from that route and send frame-ancestors with an explicit allowlist. Never remove protection site-wide to fix one embed.

A third party owns the framed page. Sites such as apple.com, whatsapp.com, and most banks block framing on purpose. You cannot override the header from your side. Proxying their page to strip the header breaks their security model and often their terms of service. Use their official embed code or API, or open the page in a new tab with a link.

The header appears twice. A web server and an application framework can both add it. Conflicting values, such as DENY and SAMEORIGIN, can be treated as invalid or handled differently by each browser. Send the header from one layer only.

Best Practices and Common Mistakes

Most X-Frame-Options problems come from a missing header on some responses or a conflict between layers.

Best practices

  • Default to DENY. Relax it to SAMEORIGIN only on routes your app frames.
  • Send CSP frame-ancestors with the same intent for modern browsers.
  • Set the header on every HTML response, including error pages and redirects.
  • Set it in one layer: web server, CDN, WAF, or application. Pick one.
  • Re-test after framework upgrades, CDN changes, or new reverse proxies.
  • Combine it with other security headers such as HSTS, X-Content-Type-Options, and a full CSP.

Common mistakes

  • Using ALLOW-FROM. Current browsers ignore it, and the page ends up unprotected.
  • Setting the header in a <meta> tag. Browsers ignore it there.
  • Sending DENY from the server and SAMEORIGIN from the app in the same response.
  • Relying on JavaScript frame-busting code alone. Attackers can bypass it with the iframe sandbox attribute.
  • Removing the header site-wide to make one embed work.
Indusface
Indusface

Indusface secures the web, API, and AI applications of thousands of organizations across 95 countries. Backed by leading institutional investors, Indusface is recognized by Gartner, Forrester, and IDC for its innovation in application security and meets globally accepted security and compliance standards, including ISO 27001, SOC 2, PCI DSS, and GDPR. Its globally distributed cloud infrastructure spans Asia, the Middle East, Europe, and North America, enabling low-latency protection and regional data residency for enterprises worldwide.

Frequently Asked Questions

X-Frame-Options, along with the CSP frame-ancestors directive. Without either, any site can load the page in an iframe.

DENY tells the browser never to display the page inside a frame. This applies to every origin, including your own site.

SAMEORIGIN allows the page inside a frame only when the framing page shares the same scheme, host, and port.

No. Browsers still enforce DENY and SAMEORIGIN. Only ALLOW-FROM is obsolete. CSP frame-ancestors is the preferred modern control, and sending both is recommended.

No. It accepts one value with no domain list. Use Content-Security-Policy: frame-ancestors with a space-separated list of origins.

The CSP frame-ancestors directive. For example: frame-ancestors https://trusted.example.com.

Modern browsers apply frame-ancestors and ignore X-Frame-Options when both are present. Older browsers use X-Frame-Options.

No. Browsers ignore X-Frame-Options in <meta http-equiv>. It must be sent as an HTTP response header.