DNS RobotDNS Propagation Checker
Ana SayfaDNSWHOISIPSSL
DNS RobotDNS Propagation Checker

Yeni nesil DNS yayılım kontrol aracı

Gizlilik PolitikasıKullanım KoşullarıHakkımızdaBlogİletişim

DNS Araçları

DNS SorgulamaDNS Hız TestiAlan Adından IP'yeNS SorgulamaMX SorgulamaTümünü gör

E-posta Araçları

SPF Kayıt KontrolüDMARC KontrolüDKIM KontrolüSMTP Test AracıE-posta Başlık AnaliziTümünü gör

Web Sitesi Araçları

WHOIS SorgulamaAlan Adı Müsaitlik KontrolüAlt Alan Adı BulucuCMS AlgılayıcıBağlantı AnaliziTümünü gör

Ağ Araçları

Ping AracıTraceroutePort KontrolüHTTP Başlık KontrolüSSL Sertifika KontrolüTümünü gör

IP Araçları

IP SorgulamaIP Adresim NedirIP Kara Liste KontrolüIP'den Hostname'eASN SorgulamaTümünü gör

Yardımcı Araçlar

QR Kod OkuyucuQR Kod OluşturucuUPI QR Code GeneratorWiFi QR Code GeneratorMors Kodu ÇeviriciTümünü gör
© 2026 DNS Robot. Geliştiren: ❤ Shaik Brothers
Tüm sistemler çalışıyor
Made with

Partner offer

ExclusiveW

Hosting for WordPress

from

$0.59/mo

  • ✓Free site migration
  • ✓24/7 expert support
  • ✓Global datacenters
Claim Deal
Ana Sayfa/Blog/X-Frame-Options Explained: Fix “Refused to Connect” in an iframe

X-Frame-Options Explained: Fix “Refused to Connect” in an iframe

Shaik Vahid1 Eyl 202612 dk okuma
X-Frame-Options explained: an iframe showing “refused to connect” next to the fix — SAMEORIGIN site-wide, CSP frame-ancestors * on the embed path
X-Frame-Options explained: an iframe showing “refused to connect” next to the fix — SAMEORIGIN site-wide, CSP frame-ancestors * on the embed path

Önemli Bilgi

“Refused to connect” inside an iframe is not a network error. The framed site sent an X-Frame-Options or CSP frame-ancestors header telling your browser not to render it inside another page. The fix lives on the framed site’s server, never in your iframe tag: keep SAMEORIGIN on the whole site and open a single embed path with frame-ancestors *.

Advertisement

What “Refused to Connect” Inside an iframe Really Means

You add an <iframe> pointing at another site, reload, and instead of the page you get a grey box with a sad-face icon and the words example.com refused to connect. Your first instinct is a network problem, because that is exactly what Chrome shows for a dead server. But open DevTools and the console tells a different story:

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

İpucu

Quick test: open the iframe’s src URL in a new tab. If it loads there but shows “refused to connect” inside your page, the cause is a framing header, and the rest of this guide applies.

Nothing failed on the network. DNS resolved, the TCP connection opened, TLS completed, and the server returned a perfectly good 200. The browser downloaded the page, read one response header, and then deliberately refused to paint it inside your page. That header is X-Frame-Options, and it exists to stop clickjacking — an attack where a hostile page loads a login or payment form inside an invisible iframe and tricks visitors into clicking buttons they cannot see.

This matters for how you troubleshoot it. A real connection failure — ERR_CONNECTION_REFUSED when you open the URL directly in a tab — is a server or firewall problem, covered in ERR_CONNECTION_REFUSED. The iframe version only appears when the page is framed, and the same URL works fine in its own tab. If the site loads directly but not in your frame, you are looking at a framing policy, not an outage.

How X-Frame-Options Works

X-Frame-Options is an HTTP response header, standardised in RFC 7034 and supported by every browser since Internet Explorer 8. The server attaches it to a page; the browser reads it before rendering and decides whether the page may appear inside a <frame>, <iframe>, <embed> or <object> on another page.

It takes one of two values that still matter, plus one that does not:

ValueWho may frame the pageTypical use
DENYNobody — not even the same siteLogin pages, admin panels, anything with a session
SAMEORIGINOnly pages on the exact same origin (scheme + host + port)Sensible default for most sites; lets you frame your own pages
ALLOW-FROM https://a.comObsolete — ignored by every modern browserDo not use; see below

Not

SAMEORIGIN compares the full origin, so https://example.com framing http://example.com is blocked, and so is example.com framing www.example.com. Mismatched subdomains are the most common surprise when your own site refuses to frame itself.

The header applies per response, not per site. That is the detail most guides skip, and the one that makes the fix in this article possible: a server can send SAMEORIGIN on 99% of its pages and a different policy on the one path that is meant to be embedded.

Advertisement

DENY vs SAMEORIGIN: Which One Should You Send?

Send SAMEORIGIN unless you have a reason not to. It blocks every third-party frame while keeping your own site free to frame its own pages — help widgets, previews, dashboards that load other internal pages.

Send DENY on pages where framing has no legitimate purpose and the downside is severe: sign-in forms, payment pages, one-click actions such as “delete account”. DENY also covers a subtle case where an attacker manages to inject a frame into one of your own pages — with SAMEORIGIN, that injected frame would still be allowed.

Whichever you pick, set it once at the web-server or CDN level so it covers every response, including error pages and static assets. Headers set inside application code tend to miss the 404 page and the maintenance page, and those are exactly the pages an attacker will frame.

ALLOW-FROM Is Dead — Do Not Use It

Older articles still recommend X-Frame-Options: ALLOW-FROM https://partner.com to allow a single embedding site. It never worked in Chrome or Safari, and Firefox dropped it in version 70 (October 2019). Worse, a modern browser that sees ALLOW-FROM treats the whole header as invalid and ignores it completely, which leaves the page frameable by anyone — the opposite of what you intended.

If you need to allow specific sites, the answer is CSP frame-ancestors, covered next. X-Frame-Options only knows how to say “nobody” or “only me”.

Uyarı

Sending ALLOW-FROM today is worse than sending nothing: the browser discards the header, and you lose the SAMEORIGIN protection you thought you had.

frame-ancestors: The CSP Directive That Replaces It

Content Security Policy Level 2 introduced frame-ancestors, a directive that does everything X-Frame-Options does and adds the one thing it cannot: a real allow-list. It rides inside the standard Content-Security-Policy header and accepts one or more sources:

  • It checks every ancestor, not just the parent. If page A frames page B and B frames your page, both A and B must be allowed. That is stricter and more predictable than X-Frame-Options, whose handling of nested frames varies between browsers.

  • It only works as an HTTP header. frame-ancestors is explicitly ignored when CSP is delivered through a <meta http-equiv> tag — the browser has to know the policy before it decides whether to render the document at all. The same is true of X-Frame-Options. Neither can be set from HTML.

  • Wildcards are per-host, not global. https://*.example.com matches any subdomain of one site; a bare * matches every origin on the internet. Fine for a calculator widget, wrong for anything that carries a session.

http
# Nobody may frame this (same as X-Frame-Options: DENY)
Content-Security-Policy: frame-ancestors 'none'

# Only my own origin (same as SAMEORIGIN)
Content-Security-Policy: frame-ancestors 'self'

# My origin plus two named partners, one with a subdomain wildcard
Content-Security-Policy: frame-ancestors 'self' https://partner.example https://*.trusted.example

# Anyone may frame this — for a public embed endpoint only
Content-Security-Policy: frame-ancestors *

Three details are worth knowing before you switch:

What Happens When Both Headers Are Set

Most sites end up sending both during a migration, and the precedence is defined: if `frame-ancestors` is present, the browser enforces it and ignores `X-Frame-Options`. The CSP Level 2 specification says so outright, and Chrome, Firefox, Safari and Edge all follow it.

That makes the safe migration path simple. Keep X-Frame-Options: SAMEORIGIN for the handful of ancient clients that predate CSP 2, add Content-Security-Policy: frame-ancestors 'self' alongside it, and every modern browser will use the CSP rule. The two only cause trouble if you make them say different things — X-Frame-Options: DENY next to frame-ancestors *, say — in which case modern browsers allow framing and only very old ones block it. Keep them consistent and there is nothing to debug.

İpucu

See what a page really sends, with both headers side by side, using the HTTP headers checker. It requests the URL server-side, so a CDN, a proxy or a browser extension cannot hide the real values from you.

Step 1: Find the Header That Is Blocking You

Before changing anything, confirm which policy is in play. The browser console already told you the header name; curl shows you the exact value the server sends. This example uses the site from the worked example further down — its main pages are deliberately locked:

  • `x-frame-options: SAMEORIGIN` or `DENY` — the classic framing policy. Only a change on that server can lift it.

  • `content-security-policy: … frame-ancestors …` — a CSP policy. Read the source list; if your origin is not in it, you are blocked. The directive is often buried in a long CSP header alongside script-src and friends, so read the whole value.

  • Neither header — the page is frameable, and “refused to connect” has another cause: a redirect to a login page that *is* locked, or the page’s own JavaScript running a frame-buster that navigates the top window.

bash
curl -sI https://emicalcs.com/ | grep -i -E 'x-frame-options|content-security-policy'

# Output:
# x-frame-options: SAMEORIGIN

Not

Redirects inside an iframe are invisible in the address bar. If curl -sI returns a 301 or 302, add -L to follow it and check the headers on the final page — that is the document the browser actually tried to frame.

One of three things comes back:

Advertisement

Step 2: Decide Who Should Be Allowed to Frame the Page

Everything from here on depends on one question: do you control the site being framed?

If you do not, stop here. There is no client-side workaround — not a sandbox attribute, not a different src, not a browser flag your visitors will have. The site owner decided their page should not be embedded, and the browser is enforcing that decision on their behalf. Your options are to link to the page instead, to ask the owner whether they provide an embed endpoint, or to use an official widget if one exists. Many services that lock their main site do offer one; the example in the next section shows what that looks like.

If you do control it, resist the temptation to remove the header site-wide. That reopens clickjacking on every page — including the ones with sessions and forms — to fix an embed on one. Instead, decide which specific paths are meant to be framed, and by whom:

SituationHeader to send on that path
Internal tool framed by your own dashboardframe-ancestors 'self' (plus X-Frame-Options: SAMEORIGIN)
Widget embedded by a few known partnersframe-ancestors 'self' https://partner-a.com https://partner-b.com
Public embeddable widget (calculator, map, player)frame-ancestors * on the embed path only
Everything elseframe-ancestors 'self' plus X-Frame-Options: SAMEORIGIN

The Right Pattern: Lock the Site, Open One Embed Path

The cleanest real-world example of this pattern I have come across is EMICalcs, a free collection of 44 Indian finance calculators — loan EMI, SIP, income tax, GST and so on — that other sites are invited to embed. The main site is locked; a dedicated /embed/ path is open. Compare the headers on the two:

bash
# The calculator page a visitor normally uses — locked
curl -sI https://emicalcs.com/income-tax-calculator/ | grep -i -E 'x-frame|frame-ancestors'
# x-frame-options: SAMEORIGIN

# The same calculator on its embed path — open to any site
curl -sI https://emicalcs.com/embed/income-tax-calculator/ | grep -i -E 'x-frame|frame-ancestors'
# content-security-policy: frame-ancestors *

Not

Whether you use a path (/embed/), a subdomain (embed.example.com) or a query flag, keep the embeddable documents separate from the interactive ones. A page that can be framed by anyone should not carry a session cookie that does anything interesting.

Notice what the embed response does not contain: there is no X-Frame-Options header at all on /embed/. That is deliberate. Because the CSP directive takes precedence, leaving SAMEORIGIN in place would be harmless in modern browsers — but it would confuse the next engineer who reads the headers, and it would still block the few old clients that only understand X-Frame-Options. The embed path sends one policy, and it says exactly one thing.

Everything else on the site keeps SAMEORIGIN, so the homepage, the article pages and the full calculator pages stay protected from clickjacking. The embed versions are stripped-down documents with no navigation and nothing worth hijacking, which is why opening them to * is a reasonable trade.

Their embed guide is worth reading as a template if you are publishing a widget of your own: one stable URL per calculator, a fixed height that fits most of them, loading="lazy" in the snippet, and a promise that retired embed URLs will redirect rather than 404 — because every site that embeds you is trusting your URL to keep working.

Advertisement

Nginx (and the add_header Trap)

Here is the same pattern in the three places most sites set headers. In each case a site-wide rule locks everything and a narrower rule overrides it for the embed path. Nginx first, because it has a trap:

nginx
# Site-wide default (server block)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
add_header X-Content-Type-Options "nosniff" always;

location /embed/ {
    # WARNING: any add_header here stops ALL add_header inheritance from the
    # server block — re-declare every header you still want on this path.
    add_header Content-Security-Policy "frame-ancestors *" always;
    add_header X-Content-Type-Options "nosniff" always;
}

Uyarı

The always flag matters too. Without it Nginx only adds the header to 2xx and 3xx responses, so your 404 and 500 pages ship with no framing protection at all.

The comment is not optional reading. add_header is not merged across levels: the moment a location block declares any add_header, it stops inheriting every add_header from the server block. Teams routinely lose their HSTS or nosniff headers on the embed path this way and only find out in a security scan months later. Re-declare what you need, and leave X-Frame-Options out of the embed block so the path sends one clear policy.

Apache

Apache’s Header directive merges across scopes, so you can set the default once and only touch the two headers that change on the embed path:

apache
# Site-wide default (needs mod_headers)
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"

<LocationMatch "^/embed/">
    Header always unset X-Frame-Options
    Header always set Content-Security-Policy "frame-ancestors *"
</LocationMatch>

Next.js and Express

In Next.js, headers are declared in next.config.js. When two rules match the same path and set the same header, the later rule wins — but a header set only by the earlier rule still applies. To keep X-Frame-Options off the embed path entirely, exclude it from the site-wide rule with a negative lookahead:

javascript
// next.config.js
module.exports = {
  async headers() {
    return [
      {
        // Every path EXCEPT /embed/... gets the locked defaults
        source: '/((?!embed/).*)',
        headers: [
          { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
          { key: 'Content-Security-Policy', value: "frame-ancestors 'self'" },
        ],
      },
      {
        source: '/embed/:path*',
        headers: [
          { key: 'Content-Security-Policy', value: 'frame-ancestors *' },
        ],
      },
    ]
  },
}

On Express, the popular helmet middleware already sends X-Frame-Options: SAMEORIGIN and a CSP that includes frame-ancestors 'self' by default. Mount it globally, then on the embed router call res.removeHeader('X-Frame-Options') and set Content-Security-Policy to frame-ancestors * before rendering. The order matters: your override has to run after helmet, or helmet will overwrite it.

Embedding a Third-Party Widget the Safe Way

Now the other side of the fence: you are the site doing the embedding, the provider has an open embed path, and you want the frame to behave. A widget snippet done well looks like this — it is the one EMICalcs publishes on each calculator page, and every attribute is doing a job:

  • `title` — names the frame for screen readers. Without it, assistive technology announces “frame” and nothing else, and accessibility audits fail the page.

  • `width="100%"` with a `max-width` in `style` — fills narrow columns on mobile without overflowing wide ones on desktop.

  • A fixed `height` — the embedding page cannot read a cross-origin frame’s content height, so a hard-coded value is the only way to avoid layout shift while it loads. Set it once per widget.

  • `loading="lazy"` — defers the request until the frame scrolls near the viewport, so a widget at the bottom of a long article costs nothing on initial load.

  • `border:0; border-radius; overflow:hidden` — removes the default inset border and clips the corners so the widget looks native to your page.

html
<iframe src="https://emicalcs.com/embed/income-tax-calculator/"
        title="Income Tax Calculator FY 2026-27 — EMICalcs"
        width="100%" height="760"
        style="max-width:680px;border:0;border-radius:16px;overflow:hidden"
        loading="lazy"></iframe>
<p>Free <a href="https://emicalcs.com/income-tax-calculator/" target="_blank" rel="noopener">Income Tax Calculator FY 2026-27</a> by EMICalcs</p>

The credit line sits deliberately *outside* the iframe. Anything inside the frame belongs to the framed document — its links are the provider’s links, on the provider’s URL — so a link inside the frame is not part of your page as far as your readers or a search engine crawler are concerned. If you want to credit the source, or the provider asks you to, put a plain paragraph next to the frame in your own HTML, as above.

sandbox: What to Allow for a Calculator Widget

The sandbox attribute is the embedder’s own protection. Add it with no value and the framed page gets nothing: no scripts, no forms, no pop-ups, no navigating your window, and an opaque origin that disables its own cookies and storage. Then add back only what the widget needs:

html
<iframe src="https://emicalcs.com/embed/loan-emi-calculator/"
        sandbox="allow-scripts allow-forms allow-same-origin allow-popups"
        title="Loan EMI Calculator — EMICalcs"
        width="100%" height="760" loading="lazy"></iframe>

İpucu

Sandboxing does not bypass X-Frame-Options or frame-ancestors. It restricts what the framed page may do *after* the browser has agreed to render it — the framing policy is checked first and still wins.

allow-scripts and allow-forms are the minimum for anything interactive. allow-same-origin lets the framed page keep its own origin so its storage and cookies work — on a cross-origin embed this is safe, because “its origin” is still the provider’s, not yours. allow-popups is only needed if the widget opens links in a new tab, as the credit link inside the EMICalcs embed does.

What you should not do is combine allow-scripts with allow-same-origin on a frame that points at your own origin. The framed script can then reach up and remove its own sandbox attribute, and you have sandboxed nothing.

Why There Is No Fix on the Embedding Side

A search for “refused to connect iframe” turns up three popular workarounds. All of them are bad, and it helps to know exactly why.

  • Browser extensions that strip `X-Frame-Options` — they only work in your own browser. Every visitor to your site still sees the grey box, and you have switched off clickjacking protection on every site you visit in the meantime.

  • Proxying the page through your own server — you fetch the third-party HTML server-side and serve it from your origin, so the header never reaches the browser. This breaks relative links, cookies, logins and anything script-driven; it puts you on the hook for their content; and it usually violates the provider’s terms. Sites that catch you doing it block your server’s IP.

  • Screenshot or “web-to-image” services — you get a static picture, not a working page. Fine for a preview thumbnail, useless for a calculator.

The header is a statement by the site owner, enforced by the browser, about what may happen to their page. The only legitimate ways through are the two already covered: the owner opens an embed path, or you link out. Providers that want to be embedded make it easy — the good ones publish a snippet, keep the URL stable and open only the path that is safe to open.

Advertisement

Verify the Headers After Every Change

Framing headers get set in a config file and then forgotten, and a CDN, a reverse proxy or a well-meaning security plugin can add, strip or duplicate them at any time. After deploying, check from outside your network:

bash
# The locked pages should show SAMEORIGIN and/or frame-ancestors 'self'
curl -sI https://example.com/ | grep -i -E 'x-frame-options|content-security-policy'

# The embed path should show ONLY frame-ancestors, with the sources you intended
curl -sI https://example.com/embed/widget/ | grep -i -E 'x-frame-options|content-security-policy'

# Error pages count too — attackers frame those as well
curl -sI https://example.com/this-does-not-exist | grep -i -E 'x-frame-options|content-security-policy'

Uyarı

A Content-Security-Policy-Report-Only header blocks nothing. If you deploy frame-ancestors in report-only mode to test it, the page stays frameable until you move the directive into the enforcing Content-Security-Policy header.

Then test it the way a browser does: put the embed URL in an <iframe> on a page served from a *different* origin — a local file:// page will not do, since browsers treat it specially — and open the console. A clean load means no Refused to display or Refused to frame line.

If the site sits behind Cloudflare or another CDN, the headers you see with curl from your laptop can differ from what the CDN serves elsewhere, or from what a cached copy carries. The HTTP headers checker fetches from a neutral vantage point and lists X-Frame-Options and Content-Security-Policy explicitly, with a note on whether each is present — a quick way to catch a CDN rule that quietly re-added SAMEORIGIN to your embed path.

Symptom → Cause → Fix

Use this to jump straight to the fix instead of re-reading the whole guide:

What you seeWhat it meansFix
Grey box “refused to connect”, but the URL loads in a tabX-Frame-Options or frame-ancestors blocked the frameOnly the site owner can change it — use their embed path or link out
Console: Refused to display … 'X-Frame-Options' to 'sameorigin'Classic header; only same-origin frames allowedOn the framed site, open the embed path with frame-ancestors * or a partner list
Console: Refused to frame … violates … frame-ancestorsCSP allow-list does not include your originAdd your origin to the framed site’s frame-ancestors sources
Worked yesterday, broken today, no code changeCDN or security plugin started injecting SAMEORIGINCheck with curl; exclude the embed path in the CDN or plugin rule
Your own dashboard cannot frame your own pageSubdomain or scheme mismatch (www vs apex, http vs https)Use frame-ancestors 'self' https://*.example.com
Embed works, but HSTS or nosniff vanished on that pathNginx add_header inheritance dropped themRe-declare every header inside the location block
ERR_CONNECTION_REFUSED when opening the URL directlyA real network failure, not a framing policySee ERR_CONNECTION_REFUSED and the port checker

If the URL fails in its own tab too, the frame is a red herring. Walk the network layers in order — DNS, port, TLS, headers — with the public endpoint testing guide before touching any framing headers.

See every security header a URL sends

Check X-Frame-Options, Content-Security-Policy, HSTS and the rest for any page — fetched server-side, so a CDN, a proxy or a browser extension cannot mask the real response. Free, no signup.

Dene HTTP Headers Checker

Advertisement

Frequently Asked Questions

Functionally it has been superseded by the CSP frame-ancestors directive, which browsers prioritise whenever both headers are present. It is not useless, though: every current browser still honours X-Frame-Options when frame-ancestors is absent, and it protects the small number of old clients that predate CSP Level 2. The practical advice is to send both and keep them consistent — SAMEORIGIN alongside frame-ancestors 'self'.

İlgili Araçlar

HTTP Headers CheckPort CheckerSSL Certificate CheckDNS Lookup

İlgili Makaleler

ERR_CONNECTION_REFUSED: Ne Anlama Gelir ve Nasıl DüzeltilirHow to Test a Public API Endpoint: DNS, Port, TLS, Headers & CORS403 Forbidden Hatasi: Ne Anlama Gelir ve Nasil DuzeltilirERR_TOO_MANY_REDIRECTS: Nasıl Düzeltilir (Tüm Tarayıcılar)

İçindekiler

  • What “Refused to Connect” Inside an iframe Really Means
  • How X-Frame-Options Works
  • DENY vs SAMEORIGIN: Which One Should You Send?
  • ALLOW-FROM Is Dead — Do Not Use It
  • frame-ancestors: The CSP Directive That Replaces It
  • What Happens When Both Headers Are Set
  • Step 1: Find the Header That Is Blocking You
  • Step 2: Decide Who Should Be Allowed to Frame the Page
  • The Right Pattern: Lock the Site, Open One Embed Path
  • Nginx (and the add_header Trap)
  • Apache
  • Next.js and Express
  • Embedding a Third-Party Widget the Safe Way
  • sandbox: What to Allow for a Calculator Widget
  • Why There Is No Fix on the Embedding Side
  • Verify the Headers After Every Change
  • Symptom → Cause → Fix
  • Sıkça Sorulan Sorular