How X-Frame-Options works
X-Frame-Options is an HTTP response header that blocks a page from loading in frames. It has two valid values, DENY and SAMEORIGIN. To allow other sites, remove it and use CSP frame-ancestors, because browsers ignore X-Frame-Options when an enforced frame-ancestors policy is present.
5 min read · Updated
The two values
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
| Value | Result |
|---|---|
DENY | No page can frame this page, not even a page on the same site. |
SAMEORIGIN | Only pages with the same origin can frame it. Every frame above it must match, not only the direct parent. |
| No header | Any site can frame the page, unless CSP frame-ancestors blocks it. |
The value is not case-sensitive. sameorigin and SAMEORIGIN do the same thing.
ALLOW-FROM does not work
Old guides show this value:
X-Frame-Options: ALLOW-FROM https://partner.example
Current browsers do not support ALLOW-FROM. The HTML standard treats it as an invalid value, and an invalid value counts as no header. So ALLOW-FROM does not limit framing to one site. It lets every site frame the page.
To allow one site, use CSP:
Content-Security-Policy: frame-ancestors 'self' https://partner.example
See CSP frame-ancestors.
When browsers ignore X-Frame-Options
The HTML standard says: if the response has an enforced CSP with a frame-ancestors directive, the browser skips the X-Frame-Options check.
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors https://partner.example
A modern browser lets https://partner.example frame this page. The DENY has no effect.
This rule has two limits:
- Report-only does not count. A
frame-ancestorsinContent-Security-Policy-Report-Onlyonly reports. It does not switch off X-Frame-Options. - A CSP without
frame-ancestorsdoes not count. A policy with onlyscript-src, for example, leaves X-Frame-Options in charge.
Do not depend on this rule to fix a page. Some security scanners and old clients still read X-Frame-Options. Remove the header when you allow framing, and keep the CSP as the one source of truth.
Duplicate and broken values
A page often gets the header twice: once from the app and once from a proxy or CDN. The browser joins the values into one list. Then it applies these rules:
| Header the browser receives | Result |
|---|---|
SAMEORIGIN, SAMEORIGIN | Same-origin frames allowed |
SAMEORIGIN, DENY | Blocked |
SAMEORIGIN, ALLOW-FROM https://partner.example | Blocked |
ALLOWALL | Allowed (invalid value, treated as no header) |
INVALID, INVALID | Allowed (treated as no header) |
Two different values that include DENY, SAMEORIGIN, or ALLOWALL block the page. Chrome prints a console message about "conflicting values" and falls back to deny.
If you see a blocked page and your config looks right, look at the raw response. You may find a second header from another layer.
A meta tag does not work
<!-- Has no effect -->
<meta http-equiv="X-Frame-Options" content="DENY" />
Browsers read X-Frame-Options only from HTTP response headers. To set it, change the server, the framework, or the CDN.
The error messages it causes
When X-Frame-Options blocks a frame, the browser shows an error page in the frame and writes a line to the console. Each message has its own page with the exact text, the cause, and the fix:
- Refused to display in a frame: X-Frame-Options sameorigin
- Refused to display in a frame: X-Frame-Options deny
- "example.com refused to connect." in Chrome and Edge
- Firefox Can't Open This Page
Set or remove the header
Each server has its own syntax:
- Nginx:
add_headerandproxy_hide_header - Apache:
Header always setandHeader always unset - Express: the
xFrameOptionsoption of Helmet - WordPress: core, plugins, and hosts can all add it
To block all framing, send both headers. Current browsers use frame-ancestors. Very old browsers only know X-Frame-Options:
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
See what your page sends
Paste the URL into the iframe tester. It tells you if the page can load in a frame, which header decides it, and how to fix it.
To check many pages at once, use the bulk check. It shows X-Frame-Options and frame-ancestors for up to 100 URLs.