iframe “A conexão foi recusada”: corrigir X-Frame-Options
Ponto-chave
“A conexão foi recusada” dentro de um iframe não é um erro de rede. O site incorporado enviou um header X-Frame-Options ou CSP frame-ancestors dizendo ao seu navegador para não exibi-lo dentro de outra página. A correção fica no servidor do site incorporado, nunca na sua tag iframe: mantenha SAMEORIGIN no site inteiro e abra um único caminho de incorporação com frame-ancestors *.
Verifique agora

Comece a ler
- O que “A conexão foi recusada” dentro de um iframe realmente significa
- Como o X-Frame-Options funciona
- DENY ou SAMEORIGIN: qual enviar?
- ALLOW-FROM morreu — não use
- frame-ancestors: a diretiva CSP que o substitui
- O que acontece quando os dois headers estão definidos
- Passo 1: encontre o header que está bloqueando
- Passo 2: decida quem pode incorporar a página
- O padrão certo: trave o site, abra um único caminho de incorporação
- Nginx (e a armadilha do add_
header) - Apache
- Next.js e Express
- Como incorporar um widget de terceiros com segurança
- sandbox: o que permitir em um widget de calculadora
- Por que não existe correção do lado de quem incorpora
- Verifique os headers a cada mudança
- Sintoma → causa → correção
- Perguntas frequentes
O que “A conexão foi recusada” dentro de um iframe realmente significa
Você adiciona um <iframe> apontando para outro site, recarrega e, em vez da página, aparece uma caixa cinza com um ícone de rostinho triste; ao passar o mouse, ela diz A conexão com example.com foi recusada. Seu primeiro instinto é suspeitar da rede, porque é exatamente o que o Chrome mostra para um servidor fora do ar. Mas abra o DevTools e o console conta outra história:
Refused to display 'https://emicalcs.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.Nada falhou na rede. O DNS resolveu, a conexão TCP abriu, o TLS terminou e o servidor devolveu um 200 perfeitamente normal. O navegador baixou a página, leu um header de resposta e então se recusou de propósito a exibi-la dentro da sua página. Esse header é o X-Frame-Options, e ele existe para impedir o clickjacking: um ataque em que uma página hostil carrega um formulário de login ou de pagamento dentro de um iframe invisível e engana os visitantes para que cliquem em botões que não enxergam.
Isso importa para o diagnóstico. Uma falha de conexão real — ERR_CONNECTION_REFUSED ao abrir a URL direto numa aba — é problema de servidor ou de firewall, tratado em ERR_CONNECTION_REFUSED. A versão com iframe só aparece quando a página está dentro de um frame, e a mesma URL funciona bem na própria aba. Se o site carrega direto mas não no seu frame, você está diante de uma política de incorporação, não de uma queda.
Veja os headers X-Frame-Options e CSP de uma página
Como o X-Frame-Options funciona
X-Frame-Options é um header de resposta HTTP, descrito pela primeira vez na RFC 7034, uma RFC informativa que o padrão HTML agora substitui, e é suportado por todos os navegadores desde o Internet Explorer 8. O servidor o anexa a uma página; o navegador o lê antes de renderizar e decide se a página pode aparecer dentro de um <frame>, <iframe>, <embed> ou <object> de outra página.
Ele aceita dois valores que ainda importam, mais um que não importa mais:
| Valor | Quem pode incorporar a página | Uso típico |
|---|---|---|
DENY | Ninguém — nem o próprio site | Páginas de login, painéis administrativos, tudo que tenha sessão |
SAMEORIGIN | Somente páginas da mesma origem exata (esquema + host + porta) | Padrão sensato para a maioria dos sites; permite incorporar suas próprias páginas |
ALLOW-FROM https://a.com | Obsoleto — ignorado por todos os navegadores modernos | Não use; veja abaixo |
O header vale por resposta, não por site. É o detalhe que a maioria dos guias pula e o que torna possível a correção deste artigo: um servidor pode enviar SAMEORIGIN em 99% das páginas e outra política no único caminho feito para ser incorporado.
DENY ou SAMEORIGIN: qual enviar?
Envie SAMEORIGIN, a menos que tenha um motivo para não fazê-lo. Ele bloqueia todo frame de terceiros e deixa seu site livre para incorporar as próprias páginas — widgets de ajuda, pré-visualizações, painéis que carregam outras páginas internas.
Envie DENY em páginas onde incorporar não tem finalidade legítima e o prejuízo seria grave: formulários de login, páginas de pagamento, ações de um clique como “excluir conta”. O DENY também cobre um caso sutil: um atacante conseguir injetar um frame em uma das suas próprias páginas — com SAMEORIGIN, esse frame injetado ainda seria permitido.
Qualquer que seja a escolha, defina uma vez no servidor web ou no CDN para cobrir todas as respostas, incluindo páginas de erro e arquivos estáticos. Headers definidos no código da aplicação costumam esquecer a página 404 e a de manutenção: defina-os onde todas as respostas passam.
ALLOW-FROM morreu — não use
Artigos antigos ainda recomendam X-Frame-Options: ALLOW-FROM https://partner.com para permitir um único site incorporador. Nunca funcionou no Chrome nem no Safari, e o Firefox o removeu na versão 70 (outubro de 2019). Pior: um navegador moderno que vê ALLOW-FROM trata o header inteiro como inválido e o ignora por completo, deixando a página incorporável por qualquer um — o oposto do que você queria.
Se precisa permitir sites específicos, a resposta é o CSP frame-ancestors, explicado a seguir. O X-Frame-Options só sabe dizer “ninguém” ou “só eu”.
frame-ancestors: a diretiva CSP que o substitui
O Content Security Policy nível 2 introduziu o frame-ancestors, uma diretiva que faz tudo o que o X-Frame-Options faz e acrescenta o que ele não consegue: uma lista de permissão de verdade. Ela vai dentro do header padrão Content-Security-Policy e aceita uma ou mais fontes:
# 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 *Três detalhes valem a pena antes de migrar:
- Ela verifica todos os ancestrais, não só o pai. Se a página A incorpora a página B e B incorpora a sua, tanto A quanto B precisam estar permitidas (CSP). O padrão HTML agora aplica o
X-Frame-Optionsa todos os ancestrais também (WHATWG), mas navegadores antigos verificavam só o pai ou a página de topo. - Só funciona como header HTTP. O
frame-ancestorsé explicitamente ignorado quando a CSP chega por uma tag<meta http-equiv>— o navegador precisa conhecer a política antes de decidir se renderiza o documento. O mesmo vale para oX-Frame-Options. Nenhum dos dois pode ser definido pelo HTML. - Curingas valem por host, não globalmente.
https://*.example.comcasa com qualquer subdomínio de um site; um*sozinho casa com todas as origens da internet. Ótimo para um widget de calculadora, errado para qualquer coisa com sessão.
O que acontece quando os dois headers estão definidos
A maioria dos sites acaba enviando os dois durante uma migração, e a precedência é definida: se frame-ancestors estiver presente, o navegador o aplica e ignora o X-Frame-Options. A especificação CSP nível 2 diz isso sem rodeios, e Chrome, Firefox, Safari e Edge a seguem.
Isso deixa a migração segura simples. Mantenha X-Frame-Options: SAMEORIGIN para os poucos clientes antigos anteriores ao CSP 2, adicione Content-Security-Policy: frame-ancestors 'self' ao lado, e todo navegador moderno usará a regra CSP. Os dois só dão problema se você os fizer dizer coisas diferentes — X-Frame-Options: DENY ao lado de frame-ancestors *, por exemplo — caso em que navegadores modernos permitem a incorporação e só os muito antigos bloqueiam. Mantenha-os coerentes e não há nada a depurar.
Passo 1: encontre o header que está bloqueando
Antes de mudar qualquer coisa, confirme qual política está em jogo. O console já informou o nome do header; o curl mostra o valor exato que o servidor envia. Este exemplo usa o site do caso prático mais adiante — suas páginas principais estão bloqueadas de propósito:
curl -sI https://emicalcs.com/ | grep -i -E 'x-frame-options|content-security-policy'
# Output:
# x-frame-options: SAMEORIGINUma de três coisas volta:
x-frame-options: SAMEORIGINouDENY— a política clássica de incorporação. Só uma mudança nesse servidor pode removê-la.content-security-policy: … frame-ancestors …— uma política CSP. Leia a lista de fontes; se a sua origem não estiver nela, você está bloqueado. A diretiva costuma ficar enterrada num header CSP longo, junto descript-srce companhia, então leia o valor inteiro.- Nenhum dos dois headers: o site incorporado permite o frame e “A conexão foi recusada” tem outra causa: um redirecionamento para uma página que está bloqueada (muitas vezes um login; siga com
curl -sIL), ou um headerCross-Origin-Embedder-Policy: require-corpna sua própria página, que bloqueia qualquer frame que não concorde e mostra a mesma caixa cinza.
Passo 2: decida quem pode incorporar a página
Tudo daqui para a frente depende de uma pergunta: você controla o site que está sendo incorporado?
Se não, pare por aqui. Não existe solução do lado do cliente — nem o atributo sandbox, nem outro src, nem uma opção de navegador que seus visitantes vão ter. O dono do site decidiu que a página não deve ser incorporada, e o navegador faz cumprir essa decisão por ele. Suas opções são linkar para a página, perguntar ao dono se ele oferece um endpoint de incorporação ou usar um widget oficial, se existir. Muitos serviços que bloqueiam o site principal oferecem um; o exemplo da próxima seção mostra como é.
Se você controla o site, resista à tentação de remover o header do site inteiro. Isso reabre o clickjacking em todas as páginas — inclusive nas que têm sessões e formulários — para consertar uma incorporação em uma só. Em vez disso, decida quais caminhos específicos devem poder ser incorporados e por quem:
| Situação | Header a enviar nesse caminho |
|---|---|
| Ferramenta interna incorporada pelo seu próprio painel | frame-ancestors 'self' (mais X-Frame-Options: SAMEORIGIN) |
| Widget incorporado por alguns parceiros conhecidos | frame-ancestors 'self' https://partner-a.com https://partner-b.com |
| Widget público incorporável (calculadora, mapa, player) | frame-ancestors * somente no caminho de incorporação |
| Todo o resto | frame-ancestors 'self' mais X-Frame-Options: SAMEORIGIN |
O padrão certo: trave o site, abra um único caminho de incorporação
O exemplo real mais limpo desse padrão que encontrei é o EMICalcs, uma coleção gratuita de 45 calculadoras financeiras indianas — parcela de empréstimo, SIP, imposto de renda, GST e outras — que outros sites são convidados a incorporar. O site principal é travado; um caminho dedicado /embed/ é aberto. Compare os headers dos dois:
# 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 *Repare no que a resposta de incorporação não contém: não há header X-Frame-Options em /embed/. É de propósito. Como a diretiva CSP tem precedência, deixar o SAMEORIGIN seria inofensivo nos navegadores modernos — mas confundiria o próximo engenheiro que ler os headers e ainda bloquearia os poucos clientes antigos que só entendem X-Frame-Options. O caminho de incorporação envia uma política, e ela diz uma coisa só.
O resto do site mantém o SAMEORIGIN, então a página inicial, os artigos e as páginas completas das calculadoras continuam protegidos contra clickjacking. As versões incorporáveis são documentos enxutos, sem navegação e sem nada que valha a pena sequestrar, e por isso abri-las para * é uma troca razoável.
O guia de incorporação deles vale a leitura como modelo se você for publicar um widget próprio: uma URL estável por calculadora, uma altura fixa que serve para a maioria, loading="lazy" no snippet e a promessa de que URLs de incorporação aposentadas vão redirecionar em vez de dar 404 — porque cada site que incorpora você confia que a sua URL continue funcionando.
Nginx (e a armadilha do add_header)
Aqui está o mesmo padrão nos três lugares onde a maioria dos sites define headers. Em cada caso, uma regra global trava tudo e uma regra mais específica a substitui no caminho de incorporação. Primeiro o Nginx, porque ele tem uma armadilha:
# 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;
}O comentário não é opcional. O add_header não é mesclado entre níveis: assim que um bloco location declara qualquer add_header, ele deixa de herdar todos os add_header do bloco server. Equipes perdem HSTS ou nosniff no caminho de incorporação assim e só descobrem meses depois, num scan de segurança. Declare de novo o que precisa e deixe o X-Frame-Options fora do bloco de incorporação para que o caminho envie uma política clara. Desde o nginx 1.29.3, add_header_inherit merge pode religar a herança (nginx), mas não use no bloco de incorporação: ele devolveria a esse caminho o X-Frame-Options e o frame-ancestors 'self' do site todo, e com dois headers CSP o navegador aplica os dois, então o frame continua bloqueado.
Apache
A diretiva Header do Apache mescla entre escopos, então você pode definir o padrão uma vez e mexer só nos dois headers que mudam no caminho de incorporação:
# 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 e Express
No Next.js, os headers são declarados em next.config.js. Quando duas regras casam com o mesmo caminho e definem o mesmo header, a última vence — mas um header definido só pela primeira regra ainda se aplica. Para manter o X-Frame-Options totalmente fora do caminho de incorporação, exclua-o da regra global com um lookahead negativo:
// 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 *' },
],
},
]
},
}No Express, o popular middleware helmet já envia por padrão X-Frame-Options: SAMEORIGIN e uma CSP que inclui frame-ancestors 'self'. Monte-o globalmente e, no router de incorporação, chame res.removeHeader('X-Frame-Options') e defina Content-Security-Policy como frame-ancestors * antes de renderizar. A ordem importa: sua sobrescrita precisa rodar depois do helmet, ou o helmet a sobrescreve.
Como incorporar um widget de terceiros com segurança
Agora, o outro lado da cerca: você é o site que incorpora, o provedor tem um caminho de incorporação aberto e quer que o frame se comporte bem. Um snippet de widget bem feito é assim; é o que o EMICalcs publica em cada página de calculadora:
<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>Cada atributo tem uma função:
title— dá nome ao frame para leitores de tela. Sem ele, a tecnologia assistiva anuncia “frame” e mais nada, e auditorias de acessibilidade reprovam a página.width="100%"com ummax-widthnostyle— preenche colunas estreitas no celular sem estourar as largas no desktop.- Um
heightfixo — a página que incorpora não consegue ler a altura do conteúdo de um frame de outra origem, então um valor fixo é a única forma de evitar salto de layout durante o carregamento. Defina uma vez por widget. loading="lazy"— adia a requisição até o frame chegar perto da área visível, então um widget no fim de um artigo longo não custa nada no carregamento inicial.border:0; border-radius; overflow:hidden— remove a borda embutida padrão e recorta os cantos para o widget parecer nativo na sua página.
A linha de crédito fica de propósito fora do iframe. Tudo dentro do frame pertence ao documento incorporado — os links são do provedor, na URL do provedor —, então um link dentro do frame não faz parte da sua página para seus leitores nem para o rastreador de um buscador. Se quiser dar crédito à fonte, ou o provedor pedir, coloque um parágrafo simples ao lado do frame no seu próprio HTML, como acima.
sandbox: o que permitir em um widget de calculadora
O atributo sandbox é a proteção de quem incorpora. Adicione-o sem valor e a página incorporada não recebe nada: nem scripts, nem formulários, nem pop-ups, nem navegação da sua janela, e uma origem opaca que desativa seus próprios cookies e armazenamento. Depois devolva apenas o que o widget precisa:
<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>allow-scripts e allow-forms são o mínimo para qualquer coisa interativa. allow-same-origin deixa a página incorporada manter a própria origem para que seu armazenamento e seus cookies funcionem — numa incorporação de outra origem isso é seguro, porque “a origem dela” continua sendo a do provedor, não a sua. allow-popups só é necessário se o widget abre links em uma nova aba, como faz o link de crédito dentro da incorporação do EMICalcs.
O que você não deve fazer é combinar allow-scripts com allow-same-origin em um frame que aponta para a sua própria origem. O script incorporado pode então subir e remover o próprio atributo sandbox, e você não terá isolado nada.
Por que não existe correção do lado de quem incorpora
Uma busca por “refused to connect iframe” traz três gambiarras populares. Todas são ruins, e vale saber exatamente por quê.
- Extensões de navegador que removem o
X-Frame-Options— só funcionam no seu próprio navegador. Todo visitante do seu site continua vendo a caixa cinza, e enquanto isso você desligou a proteção contra clickjacking em todos os sites que visita. - Passar a página pelo seu próprio servidor — você baixa o HTML de terceiros no servidor e o serve a partir da sua origem, de modo que o header nunca chega ao navegador. Isso quebra links relativos, cookies, logins e tudo que depende de scripts; faz de você responsável pelo conteúdo deles; e normalmente viola os termos do provedor. Sites que percebem bloqueiam o IP do seu servidor.
- Serviços de captura de tela ou “web para imagem” — você recebe uma imagem estática, não uma página funcionando. Serve como miniatura de pré-visualização, não para uma calculadora.
O header é uma declaração do dono do site, aplicada pelo navegador, sobre o que pode acontecer com a página dele. Os únicos caminhos legítimos são os dois já vistos: o dono abre um caminho de incorporação ou você linka para a página. Provedores que querem ser incorporados facilitam — os bons publicam um snippet, mantêm a URL estável e abrem só o caminho seguro de abrir.
Verifique os headers a cada mudança
Headers de incorporação são definidos em um arquivo de configuração e depois esquecidos, e um CDN, um proxy reverso ou um plugin de segurança bem-intencionado pode adicioná-los, removê-los ou duplicá-los a qualquer momento. Depois de publicar, confira de fora da sua rede:
# 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 should carry the header too
curl -sI https://example.com/this-does-not-exist | grep -i -E 'x-frame-options|content-security-policy'Depois teste como um navegador faz: coloque a URL de incorporação em um <iframe> numa página servida de uma origem diferente — uma página local file:// não serve, pois os navegadores a tratam de forma especial — e abra o console. Um carregamento limpo significa nenhuma linha Refused to display nem Refused to frame.
Se o site está atrás do Cloudflare ou de outro CDN, uma cópia em cache ou uma regra de borda pode levar headers diferentes dos da sua origem. O Verificador de headers HTTP pede a URL a partir do nosso servidor, fora do seu navegador e da sua rede, e mostra X-Frame-Options e Content-Security-Policy com um veredito de incorporação (SAMEORIGIN ou DENY, via CSP frame-ancestors, ou não enviado): um jeito rápido de pegar uma regra de CDN que recolocou SAMEORIGIN no seu caminho de incorporação sem avisar.
Sintoma → causa → correção
Use isto para ir direto à correção em vez de reler o guia todo:
| O que você vê | O que significa | Correção |
|---|---|---|
| Caixa cinza “A conexão foi recusada”, mas a URL carrega numa aba | X-Frame-Options ou frame-ancestors bloqueou o frame | Só o dono do site pode mudar — use o caminho de incorporação dele ou linke |
Console: Refused to display … 'X-Frame-Options' to 'sameorigin' | Header clássico; só frames de mesma origem são permitidos | No site incorporado, abra o caminho de incorporação com frame-ancestors * ou uma lista de parceiros |
Console: Refused to frame … violates … frame-ancestors | A lista de permissão da CSP não inclui a sua origem | Adicione a sua origem às fontes frame-ancestors do site incorporado |
| Funcionava ontem, quebrou hoje, sem mudança de código | Um CDN ou plugin de segurança começou a injetar SAMEORIGIN | Confira com curl; exclua o caminho de incorporação na regra do CDN ou do plugin |
| Seu próprio painel não consegue incorporar sua própria página | Subdomínio ou esquema diferente (www x domínio raiz, http x https) | Use frame-ancestors 'self' https://example.com https://*.example.com (o curinga não cobre o domínio raiz) |
A incorporação funciona, mas HSTS ou nosniff sumiram nesse caminho | A herança do add_header do Nginx os descartou | Declare de novo todos os headers dentro do bloco location |
ERR_CONNECTION_REFUSED ao abrir a URL direto | Uma falha de rede real, não uma política de incorporação | Veja ERR_CONNECTION_REFUSED e o verificador de portas |
Se a URL também falha na própria aba, o frame é pista falsa. Percorra as camadas de rede em ordem — DNS, porta, TLS, headers — com o guia para testar um endpoint de API público antes de mexer em qualquer header de incorporação.
Veja todos os headers de segurança que uma URL envia
Confira X-Frame-Options, Content-Security-Policy, HSTS e o resto de qualquer página, buscados dos nossos servidores, para que uma extensão do navegador ou um proxy da sua própria rede não altere o que você vê. Grátis, sem cadastro.
Fontes
Perguntas frequentes
O X-Frame-Options está obsoleto?
O que significa X-Frame-Options: DENY?
Qual a diferença entre DENY e SAMEORIGIN?
Como contornar o X-Frame-Options para incorporar um site?
Como definir o X-Frame-Options como DENY?
Por que meu iframe diz “A conexão foi recusada” se o site funciona em uma nova aba?
Posso definir X-Frame-Options ou frame-ancestors em uma meta tag?
frame-ancestors * deixa meu site inseguro?
Por que o X-Frame-Options: ALLOW-FROM não funciona?
Qual header vence se X-Frame-Options e frame-ancestors estiverem definidos?
Artigos Relacionados
Ferramentas Relacionadas
Verificador de headers HTTP
Analise cabeçalhos de resposta HTTP e classificação de cabeçalhos de segurança
Verificador de Portas
Teste a conectividade de portas e disponibilidade de serviços
Verificador de Certificado SSL
Verifique a validade e detalhes de segurança do certificado SSL
Consulta DNS
Todos os registros DNS direto dos servidores do domínio, ao lado do cache do Google e da Cloudflare



