"Refused to connect" in an iframe usually means the embedded site sends an X-Frame-Options or CSP frame-ancestors header that forbids your page from framing it. Open the browser console to confirm which one. If you own the site, change those headers. If you don't, use its official embed URL or link to it instead.
What "refused to connect" means in an iframe
Chrome and Edge show a grey box with a sad-face icon and the text "example.com refused to connect." Despite the wording, the server usually answered just fine. The browser received the page, read its response headers, and decided it wasn't allowed to render inside your frame.
That decision is made by the browser on behalf of the framed site, to protect it from clickjacking attacks. It's why the same URL opens fine in a new tab.
Chrome also shows the same grey box for genuine network failures, such as a server that is down, so the message alone doesn't tell you the cause. Firefox and Safari show their own error pages or simply a blank frame. In every browser, the console tells you the real reason.
How to find the exact cause
1. Read the console message
Open DevTools (F12, or Cmd+Option+I on a Mac), go to Console and reload. Chrome's messages look roughly like this (wording varies by version):
Refused to display 'https://example.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
Refused to frame 'https://example.com/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self'".
Mixed Content: The page at 'https://your-site.com/' was loaded over HTTPS, but requested an insecure frame 'http://example.com/'. This request has been blocked; the content must be served over HTTPS.
The first points to X-Frame-Options, the second to the site's CSP frame-ancestors, the third to mixed content. If a message mentions frame-src or child-src instead, with no "ancestor", the block comes from your own page's CSP, not the framed site.
2. Check the response headers in the Network tab
In the Network tab, reload, filter by Doc, click the request for the framed URL and open Headers. Under Response Headers, look for x-frame-options and content-security-policy. Check the status code too: a 301 or 302 means the frame was redirected, and the headers that count are on the final response.
From a terminal, this follows redirects and prints only the lines that matter:
curl -sL -D - -o /dev/null https://example.com | grep -iE '^(http|location|x-frame-options|content-security-policy)'
3. Run it through an iframe tester
testiframe.com does both steps at once. It loads the URL in a live iframe while a server check follows every redirect, reads the headers and names the blocker: X-Frame-Options, frame-ancestors, mixed content, likely frame-busting scripts, bot challenges, or cookies that won't survive per browser. Try it on a site that blocks framing, such as github.com.
Every cause of "refused to connect" and how to fix it
X-Frame-Options: DENY or SAMEORIGIN
The oldest framing control. DENY blocks every parent page. SAMEORIGIN allows only pages on the same origin (scheme, host and port). Plenty of software sends it by default: WordPress on its login and admin pages, Rails apps (SAMEORIGIN), Helmet in Express (SAMEORIGIN), and Django and Spring Security (DENY).
Fix: only the site owner can change it, by removing the header or replacing it with a frame-ancestors allowlist. The old ALLOW-FROM value won't help, because modern browsers ignore it.
CSP frame-ancestors
Content-Security-Policy: frame-ancestors 'self' (or 'none', or a list of origins) is the modern control. When it's present, browsers ignore X-Frame-Options, so a site can send X-Frame-Options: DENY and still allow you through frame-ancestors. Every ancestor must match, not just the direct parent: if your page is itself framed by another site, that site must be allowed too.
Fix: the owner adds your origin to the list. The X-Frame-Options vs frame-ancestors guide covers the syntax and precedence in detail.
Mixed content
An http:// iframe inside an https:// page is blocked, and Firefox and Safari often just show an empty frame. The same happens when an HTTPS URL redirects to HTTP. Use the https:// version of the URL; if the site has no HTTPS, it can't be framed on an HTTPS page. http://localhost and http://127.0.0.1 are exceptions: browsers treat them as potentially trustworthy, so local development frames still load.
Your page's own CSP (frame-src)
If your site sends a CSP with frame-src (or child-src, or default-src, which it falls back to), only listed origins can load in your frames. This one is on your side. Add the origin you're embedding:
Content-Security-Policy: frame-src 'self' https://www.youtube-nocookie.com https://player.vimeo.com
Sandbox restrictions
A sandbox attribute doesn't cause "refused to connect", but it often produces a blank or broken frame that looks like the same problem. An empty sandbox blocks scripts, forms and popups, and gives the frame an opaque origin, so JavaScript-rendered apps show nothing and anything that touches cookies or storage fails. Add back only the tokens you need:
<iframe src="https://example.com/widget" title="Booking widget"
sandbox="allow-scripts allow-forms allow-popups"></iframe>
allow-same-origin restores the frame's real origin, which cookies and storage need. Don't combine it with allow-scripts for content from your own origin, because the frame could then remove its own sandbox. Every token is explained in the iframe sandbox attribute guide.
Frame-busting JavaScript
Some sites check whether they're framed and break out:
if (window.top !== window.self) {
window.top.location = window.location.href;
}
The symptom is different: your whole tab navigates to the framed site, or the frame loads and then goes blank as the page hides itself. Only the owner can remove the script. A sandbox without allow-top-navigation stops the redirect, but many such pages then hide their content, and working against a site's explicit wish not to be framed is a poor foundation for a product.
Login and cookie redirects
The URL you framed may allow framing, but if the frame isn't logged in it redirects to a login page, and login pages very often send X-Frame-Options: DENY. The frame is often logged out even when another tab isn't, because a cross-site iframe is a third-party context: cookies without SameSite=None; Secure aren't sent, Safari blocks third-party cookies by default, and Firefox partitions them per top-level site. The Network tab shows a 302 to /login or an SSO domain. The fixes are in the iframe cookies guide.
Bot protection and WAF challenges
Sites behind Cloudflare or another WAF may answer with a challenge page. Challenges often can't be completed inside a cross-site iframe, because the challenge page may block framing or depend on cookies the browser won't keep in a third-party context. You'll see a spinner, a blank frame or "refused to connect". Only the site owner can relax the rule for embedded traffic.
The site is down or redirecting
DNS failures, refused connections, certificate errors and timeouts produce error pages too, and in Chrome the connection-refused case uses the same grey "refused to connect" box. Open the URL in a new tab: if it fails there, it isn't an iframe problem. Redirects also catch people out. https://example.com might redirect to www, a regional domain or a consent page with stricter headers, so frame the final URL directly.
Troubleshooting checklist
| What you see | Likely cause | Fix |
|---|---|---|
| Console: "set 'X-Frame-Options' to 'deny'" or "'sameorigin'" | X-Frame-Options on the framed site | Owner removes it or switches to frame-ancestors; otherwise use an official embed URL |
| Console: "an ancestor violates ... frame-ancestors" | The site's CSP frame-ancestors | Owner adds your origin (and any outer frames) |
| Console: "Mixed Content ... insecure frame" | http:// URL on an https:// page | Use the HTTPS URL |
| Console: "violates ... frame-src" | Your own page's CSP | Add the origin to your frame-src |
| Blank frame, "Blocked script execution ... sandboxed" | sandbox attribute | Add the tokens the page needs |
| Whole tab jumps to the framed site | Frame-busting JavaScript | Owner removes it; link out instead |
| Network: 302 to a login or SSO page | Missing third-party cookies, login page blocks framing | Cookie fixes, Storage Access API, or open login in a new tab |
| Spinner, challenge page or 403 | Bot protection or WAF | Owner allows embedded traffic |
| Same error in a new tab | Site down, DNS or TLS problem | Fix the site or wait |
| One URL works, another on the same site doesn't | Different headers per path, or a redirect | Frame the final URL; check headers on each |
How to fix it if you own the site
Replace X-Frame-Options with a frame-ancestors allowlist. Keep 'self' if your own pages frame each other, and list each site that should be able to embed you.
Nginx
# In the server block. Remove any "add_header X-Frame-Options" line.
add_header Content-Security-Policy "frame-ancestors 'self' https://your-site.com" always;
# If an app behind proxy_pass sets X-Frame-Options, hide it:
proxy_hide_header X-Frame-Options;
An add_header inside a location block drops every add_header inherited from the server block, so check nested blocks.
Apache
# .htaccess or <VirtualHost>, requires mod_headers
Header unset X-Frame-Options
Header always unset X-Frame-Options
Header always set Content-Security-Policy "frame-ancestors 'self' https://your-site.com"
Express with Helmet
app.use(helmet({
xFrameOptions: false, // called "frameguard" in older Helmet versions
contentSecurityPolicy: {
directives: { frameAncestors: ["'self'", "https://your-site.com"] },
},
}));
Next.js, Vercel, Netlify, Cloudflare Pages and IIS configs are in the frame-ancestors recipes.
If you already send a Content-Security-Policy, add frame-ancestors to it. A second CSP header is enforced alongside the first, so a leftover frame-ancestors 'none' in either one still blocks. Check your CDN, load balancer and framework too, since any of them can add headers.
What to do if you don't own the site
You can't override these headers from the embedding page. The visitor's browser enforces them, and no iframe attribute or script changes that. You have three honest options.
Use the site's official embed URL
Most services that want to be embedded offer a separate URL built for it:
| Service | Blocked URL | Embed URL |
|---|---|---|
| YouTube | youtube.com/watch?v=ID | https://www.youtube.com/embed/ID or youtube-nocookie.com/embed/ID |
| Google Maps | google.com/maps/place/... | Share → Embed a map, which gives google.com/maps/embed?pb=... |
| Google Docs, Sheets, Slides | The /edit URL | File → Share → Publish to web → Embed |
| Vimeo | vimeo.com/ID | https://player.vimeo.com/video/ID |
For anything else, look for Share → Embed in the product. The guide to embedding a website in HTML covers the iframe attributes to use once you have a working URL.
Link out or open a popup
A new tab keeps the user's login and cookies working, which an iframe often can't:
<a href="https://example.com/report" target="_blank" rel="noopener">Open the report</a>
<button type="button" onclick="window.open('https://example.com/report', 'report', 'width=900,height=700')">
Open report
</button>
Call window.open from a click handler, or popup blockers will stop it.
Ask the owner
Send the exact origin you'd embed from and suggest the header: frame-ancestors 'self' https://your-site.com. Many SaaS tools already have an "allowed domains" setting for embeds.
Why proxying or stripping headers is a bad idea
A common suggestion is to fetch the page through your own server and delete the headers, or to install an extension that removes them. It works in a demo and fails in practice:
- Logins break. The proxied page runs on your domain, so the site's cookies, OAuth redirects and CSRF checks no longer line up.
- It's a security hole. Their HTML and scripts now run on your origin, with access to your cookies and storage. Anything malicious in that page becomes an XSS on your site. It's also the pattern phishing kits use, which gets domains flagged.
- Terms and copyright. The header is the owner's explicit statement that the page shouldn't be framed. Republishing it under your domain can breach their terms of service and copyright.
- It doesn't last. Relative URLs, scripts and API calls break, and bot protection soon blocks your server's IP.
- Extensions only change your browser. Your visitors still see the error.
An official embed URL, a link or popup, or the owner's permission. For the owner, a correct frame-ancestors header is a one-line change.
FAQ
Why does my iframe say "refused to connect"?
Usually because the embedded site sends an X-Frame-Options or CSP frame-ancestors header that doesn't allow your page to frame it. Less often it's mixed content, a login redirect, bot protection, or the site being down. The browser console names the exact reason.
How do I fix "refused to display in a frame because it set X-Frame-Options to sameorigin"?
The site only lets its own pages frame it. If you own it, remove X-Frame-Options and send Content-Security-Policy: frame-ancestors 'self' https://your-site.com. If you don't, use the site's official embed URL or link to the page instead.
Can I bypass X-Frame-Options from my own page?
No. The browser enforces it on behalf of the framed site, and nothing in your HTML, JavaScript or iframe attributes overrides it. Proxies that strip the header break logins, create security risks and can breach the site's terms.
Why is my iframe blank with no error?
Check for a sandbox attribute without allow-scripts, mixed content (Firefox and Safari often show an empty frame), a frame-busting script that hid the page, or a bot-protection challenge. The console and Network tab usually show which one.
Why does my iframe work on localhost but not in production?
Common reasons: the production page is HTTPS and the iframe URL is http://, your production site sends a CSP with a frame-src rule, or the framed site's frame-ancestors list includes your dev origin but not your live domain.
Does "refused to connect" mean the website is down?
Not usually. Chrome uses the same message for blocked frames and refused connections. Open the URL in a new tab: if it loads there, the site is up and is blocking framing.