Loops de desconexão de WebSocket: conserte recursos em tempo real após o lançamento
Loops de desconexão WebSocket podem arruinar apps em tempo real após o lançamento. Aprenda a depurar autenticação, timeouts, escala e adicionar fallback seguro.

Por que recursos em tempo real quebram após o lançamento
“Em tempo real” normalmente significa que a tela atualiza sem dar refresh. Mensagens de chat aparecem instantaneamente, presença mostra quem está online, dashboards atualizam e alertas disparam no momento em que algo muda.
Esses recursos podem parecer perfeitos em um demo e depois falhar após o lançamento porque produção se comporta de forma diferente. Mais usuários significam mais conexões simultâneas. Um proxy ou load balancer fica entre o browser e seu servidor. Tokens expiram. Telefones trocam de rede, entram em sleep e voltam. Qualquer um desses fatores pode transformar uma conexão estável em um loop de desconexões e reconexões.
Para os usuários, um loop de desconexão WebSocket parece “funciona um segundo, depois quebra”. Mensagens chegam atrasadas ou não chegam. A UI pisca “reconectando…” repetidamente. Presença oscila. Dashboards congelam e depois pulam. Às vezes os usuários recebem duplicatas porque o cliente reenvia após reconectar.
O que muda depois do lançamento
Algumas mudanças previsíveis empurram os sockets para o limite: mais conexões concorrentes do que você testou localmente, proxies que time out de conexões “inativas”, autenticação de WebSocket que falha durante refresh ou rotação de tokens, quedas de rede móvel que disparam reconexões agressivas, e múltiplas instâncias de servidor sem estado compartilhado (ou sem sticky sessions quando você depende de estado em memória).
Confiabilidade de socket não é “nunca desconectar”. Desconexões vão acontecer. Confiabilidade significa que o app se recupera rápido e com segurança, e que não perde eventos importantes. Perder um indicador de digitação é aceitável. Perder “mensagem enviada”, status de pedido ou estado de pagamento não é.
Identifique o padrão antes de mudar qualquer coisa
Quando o tempo real quebra após o lançamento, a forma mais rápida de perder tempo é “consertar” código às cegas. Comece descrevendo o loop tão claramente que você possa prever a próxima desconexão.
Nomeie o sintoma que você realmente vê. “Está instável” esconde pistas. “Reconecta a cada 6 segundos e duplica notificações” é algo que você pode rastrear.
Antes de tocar em configurações, responda algumas perguntas:
- Quais clientes são afetados (web, iOS, Android, um navegador específico)?
- Em qual ambiente (local, staging, apenas produção)?
- Atinge todo mundo ou contas específicas?
- Acontece em horários de pico ou logo após deploys?
Então capture evidências enquanto está acontecendo. Um snapshot curto vale mais do que horas de suposições. No mínimo, colete timestamps (cliente e servidor), um ID de usuário ou sessão, um ID de conexão que você gera por socket, o código de fechamento e a razão (se disponível), e os últimos eventos antes da queda (connect, auth, subscribe, ping/pong).
Para separar desconexões do servidor de quedas do cliente, compare timelines. Se o cliente mostra o código 1006 (fechamento anormal) e o servidor não registra um close limpo, suspeite de rede, timeouts de proxy ou o app entrando em sleep. Se o servidor fecha logo após “auth” ou “subscribe”, suspeite da sua própria lógica (token ruim, permissões faltando, exceções lançadas).
Um truque prático: reproduza com um usuário em uma aba primeiro. Se não conseguir, o gatilho pode ser relacionado à carga.
Passo a passo: debug de um loop de desconexão
Quando você vê um loop de desconexão, resista ao impulso de ajustar timeouts primeiro. Torne o loop visível: o que aconteceu imediatamente antes do socket cair, e quem está agendando a reconexão.
Comece com logs simples ao redor do ciclo de vida do socket. Você quer uma história limpa do começo ao fim: connect, auth, subscription, fluxo de mensagens, então a razão do close. Inclua timestamps e um curto ID de conexão para que múltiplas abas não se confundam.
Logue o básico em ordem: connect started/open, auth enviado e sucesso/falha, subscribe enviado e ack, close/error (código e razão), e reconexão agendada (e por quem).
Depois reproduza com a configuração mínima: um usuário, uma aba, sem jobs em background. Quando falhar de forma confiável, adicione complexidade passo a passo (segunda aba, segundo usuário, maior volume de mensagens). Isso lhe diz se o gatilho é carga, concorrência ou uma subscription específica.
Em seguida, inspecione códigos de fechamento e erros. Fechamentos por política apontam para autenticação ou regras de origem. Timeouts geralmente apontam para heartbeats, proxies ou servidor bloqueado. Fechamentos anormais frequentemente significam que algo crashou ou a rede sumiu sem um close apropriado.
Também verifique se você não tem dois mecanismos de reconexão ao mesmo tempo: seu código mais a reconexão padrão da biblioteca de socket. Isso pode criar tempestades de reconexão mesmo quando o problema subjacente é pequeno.
Por fim, teste de uma rede diferente (hotspot móvel vs Wi‑Fi do escritório). Se acontece só em uma rede, foque em proxies, VPNs, portais cativos ou timeouts agressivos de inatividade.
Autenticação nos sockets: onde costuma dar errado
Muitos bugs que parecem “de rede” são na verdade bugs de autenticação. O app carrega bem, chamadas HTTP funcionam, e então o recurso ao vivo fica preso reconectando.
Três configurações comuns de autenticação
A maioria dos apps autentica sockets de três formas: reutiliza sessão via cookie, envia um bearer token na conexão, ou busca um token curto de uso único para socket via HTTPS antes. Todos podem funcionar, mas cada um tem modos comuns de falha.
Um desencontro clássico: as requisições HTTP normais estão autenticadas, mas o handshake do WebSocket não está. Cookies podem ser enviados para sua API mas bloqueados para o socket por regras cross‑origin. Ou o servidor espera um header Authorization, mas a biblioteca cliente só consegue enviar o token via query param ou subprotocol.
Falhas comuns pós‑lançamento:
- Cookies não são incluídos por causa de SameSite, Secure ou configurações de domínio (funciona no localhost, quebra no domínio real).
- O socket conecta antes da sessão estar pronta.
- O socket mantém um access token desatualizado após refresh e é expulso repetidamente.
- O servidor fecha como “unauthorized” e o cliente reconecta instantaneamente, criando muito ruído.
Trate fechamentos não autorizados sem reconexões infinitas
Trate falhas de auth diferente de falhas de rede instáveis. Se o servidor fechar com um código ou mensagem relacionada à auth, pare o loop de reconexão e recupere a sessão primeiro. Atualize o token (ou solicite login), então abra um socket novo com as novas credenciais.
Se precisar tentar novamente, use backoff (1s, 2s, 5s, 10s) e adicione jitter para que muitos clientes não reconectem ao mesmo tempo.
Um cenário comum: um dashboard roda por uma hora, o token é renovado, mas o socket continua enviando o token antigo e é fechado a cada poucos segundos. A correção não é “mais retries”. A correção é reiniciar o socket quando o token muda.
Heartbeats, timeouts e comportamento de reconexão
Muitos loops de desconexão se reduzem a isto: a conexão fica inativa e algo no meio mata ela. Esse “algo” pode ser seu servidor, um proxy ou load balancer, uma CDN, Wi‑Fi de hotel, ou um telefone que suspende atividade em background.
A solução mais comum é um heartbeat mais um comportamento de reconexão sensato. Heartbeats podem ser ping/pong (melhor quando sua biblioteca dá suporte) ou uma pequena mensagem de keepalive a nível de aplicação. De qualquer forma, você quer tráfego suficiente para que intermediários não marquem o socket como inativo.
Seja conservador no timing. Muitos proxies fecham conexões inativas em torno de 30 a 60 segundos. Um ponto de partida comum é heartbeat a cada 15 a 25 segundos e um timeout do cliente após 2 a 3 heartbeats perdidos. Muito agressivo drena bateria e dados no mobile. Muito lento morre silenciosamente.
A lógica de reconexão é a outra metade. Reconexões instantâneas podem criar uma tempestade, especialmente após um deploy ou uma breve indisponibilidade. Use backoff exponencial com jitter e um teto, resete o backoff apenas depois da conexão se manter saudável por uma pequena janela, e faça a reconexão idempotente: re‑autentique e re‑subscrua, mas não duplique subscriptions.
Conexões meio‑abertas são o caso sorrateiro: o cliente pensa que está conectado, mas o servidor se foi. Timeouts de heartbeat permitem detectar isso rapidamente.
Proxies e load balancers: causas ocultas de desconexão
Se o tempo real funcionou localmente mas oscila em produção, cheque o caminho de rede antes de reescrever código de socket. Reverse proxies, CDNs e load balancers podem fechar conexões inativas, rotacionar instâncias ou remover cabeçalhos.
O que proxies mudam sobre WebSockets
Um WebSocket começa como uma requisição HTTP e depois faz upgrade. Tudo que estiver na frente da sua aplicação precisa suportar esse upgrade e manter a conexão aberta. Muitos setups também aplicam timeouts de inatividade ou idade máxima de conexão. Se sua app só envia dados quando o usuário clica, a conexão pode parecer inativa e ser cortada.
Sticky sessions são outra armadilha. Se você mantém estado importante em memória (subscriptions, associação a salas, contexto do usuário), e o load balancer enviar uma reconexão para uma instância diferente, o usuário “conecta” mas perde eventos ou falha em checagens de estado. Estado compartilhado (Redis, banco, message broker) reduz a necessidade de stickiness.
Terminação TLS e surpresas com cabeçalhos de auth
Quando TLS é terminado no proxy, sua app pode ver a requisição como HTTP a menos que os headers encaminhados estejam configurados corretamente. Isso pode quebrar checagens como “apenas permitir cookies seguros” ou regras estritas de origem. Alguns proxies também removem ou renomeiam cabeçalhos, o que quebra autenticação baseada em token.
Para confirmar o que está fechando a conexão, compare códigos de fechamento nos dois lados, procure mensagens de log do proxy como “upstream timeout” ou “idle timeout”, aumente temporariamente timeouts de inatividade para ver se o problema para, e verifique se upgrade e forwarded headers chegam até a app.
Escalando recursos em tempo real sem perder eventos
O tempo real geralmente funciona em staging porque há um servidor. Após o lançamento, aparece uma segunda instância (ou sua plataforma começa a mover tráfego), e mensagens desaparecem. Broadcasts só alcançam usuários na mesma máquina. Salas e presença ficam inconsistentes. Reconexões podem cair em um servidor que não reconhece o estado do cliente.
A primeira regra: não mantenha estado importante de socket apenas em memória. Isso inclui estado de subscriptions, mapeamentos usuário→socket, presença e o último ID de evento recebido. Estado em memória desaparece no deploy e difere por servidor.
A maioria dos apps acaba usando um destes padrões: pub/sub compartilhado para que qualquer servidor publique e todos entreguem, um serviço dedicado em tempo real que possua conexões enquanto servidores de API ficam stateless, ou uma fila para eventos que não podem se perder para que possam ser re‑tentados com segurança.
Reconexões é onde duplicatas se escondem. Use um ID de evento (ou número de sequência) por canal e faça o cliente enviar “último recebido”. No servidor, mantenha handlers idempotentes para que processar o mesmo evento duas vezes não cobre em dobro, não crie registros duplicados e não envie notificações duplicadas.
Deploys também precisam de um plano. Se você reiniciar servidores sem aviso, força reconexões em massa e condições de corrida. Adicione um passo de drenagem: pare de aceitar novas conexões na instância antiga, deixe as existentes terminarem e então finalize.
Fallbacks que mantêm o app utilizável
Tempo real é ótimo até não ser. Quando usuários encontram loops de desconexão, você quer duas coisas: sockets estáveis e um app que ainda funcione quando os sockets não estiverem estáveis.
WebSockets brilham em interação bidirecional (chat, multiplayer, cursores ao vivo). Se o cliente recebe principalmente atualizações (mudanças de status, notificações, dashboards), Server‑Sent Events (SSE) pode ser mais simples e mais confiável porque usa uma conexão HTTP padrão e geralmente se comporta melhor através de proxies.
Um fallback prático é degradação controlada: tente WebSockets, mude para SSE se o socket não abrir ou cair com muita frequência, caia para polling curto se necessário, e se a reconexão continuar falhando, coloque o app em modo limitado (somente leitura ou “enviar depois”) enquanto tenta silenciosamente em segundo plano.
Mantenha a UI honesta. Mostre o estado da conexão (Conectado, Reconectando, Offline) e o horário da última atualização. Um botão “Tentar agora” ajuda quando alguém acabou de trocar de rede.
No servidor, projete streams para que um cliente possa retomar após reconectar. Envie eventos com IDs ou timestamps e permita “tudo desde X”. Para SSE, você pode usar Last‑Event‑ID. Para WebSockets, use um cursor de resumição ou token na conexão.
Erros comuns que tornam sockets frágeis
Nem toda desconexão é um bug. Redes móveis caem. Laptops dormem. Browsers pausam abas em background. A parte frágil é quando seu app trata desconexões normais como emergências e tenta reconectar tão agressivamente que cria uma queda auto‑infligida.
Um erro de segurança evitável é colocar segredos na URL. Query strings são copiadas em logs, analytics, relatórios de erro e screenshots. Se seu token de socket está na URL, assuma que ele vai vazar. Também é fácil logar tokens por acidente ao despejar dados do handshake durante o debug.
Desenvolvimento local pode enganar você. No localhost não há proxy corporativo, load balancer ou política de timeout de inatividade. Em produção, um proxy pode fechar conexões inativas, remover cabeçalhos ou bloquear pedidos de upgrade.
Alguns padrões que normalmente tornam sockets frágeis:
- Loops de retry sem backoff ou jitter.
- Autenticação em query strings, ou logs que capturam tokens.
- Sem limites server‑side em tentativas de reconexão por usuário/IP.
- Lógica de reconexão que reinscreve sem controle e cria listeners duplicados.
- Pular testes atrás de proxy ou load balancer.
Subscriptions duplicadas são especialmente sorrateiras. Após reconectar, o cliente pode reentrar na mesma sala ou registrar o mesmo handler novamente enquanto o servidor nunca limpa o antigo. Corrija isso tornando subscriptions idempotentes por conexão e rastreando IDs de conexão para que um socket novo possa substituir o antigo limpidamente.
Verificações rápidas antes de liberar a correção
Antes de liberar uma mudança em WebSocket, faça uma checagem rápida no cliente, no servidor e na infraestrutura. A maioria dos loops de desconexão não é um bug só. São dois ou três problemas pequenos que só aparecem juntos.
Checagens no lado do cliente
Seu cliente deve se manter calmo diante de falhas. Use backoff com jitter e teto, mostre o estado da conexão na UI, adicione lógica de resumição (último ID ou versão) para que quedas breves não percam dados, dedupe eventos para que reconexões não apliquem atualizações em dobro e feche sockets corretamente no logout ou troca de conta.
Checagens no servidor e infraestrutura
Torne desconexões compreensíveis. Se o servidor fechar uma conexão, deve ser por uma razão clara, e uma linha de log deve explicar isso. Use códigos de close explícitos, aplique auth no connect e em mensagens sensíveis, configure heartbeats e timeouts para que clientes saudáveis não sejam expulsos, limite conexões por usuário/IP e confirme configurações de WebSocket no proxy/load balancer (incluindo timeouts de inatividade e se stickiness é necessária).
Uma regra rápida: se você não consegue explicar uma desconexão a partir de uma linha de log, você não está pronto para liberar.
Testes rápidos que pegam regressões
Execute alguns cenários que frequentemente reproduzem loops: um usuário com muitas abas enquanto faz login e logout, muitos usuários conectando de uma vez (mesmo um teste de carga pequeno), deploy com usuários conectados e observe o comportamento de reconexão, e simule redes instáveis (alternar Wi‑Fi/celular, colocar o laptop pra dormir) para confirmar que o app se recupera.
Como “pronto” deve parecer: conexões se mantêm por períodos previsíveis, reconexões desaceleram em vez de acelerar, e quando uma queda acontece você consegue apontar para uma causa clara.
Um exemplo realista e próximos passos
Um fundador lança um dashboard de vendas ao vivo. Em staging parece perfeito. No dia do lançamento, tickets de suporte chegam: a página pisca “Reconectando…” a cada poucos segundos, e alguns usuários nunca recebem atualizações ao vivo.
A primeira pista está nos logs do servidor: muitas conexões terminam logo depois que o access token expira. No cliente, o app reconecta rapidamente mas continua reutilizando o mesmo token expirado, então é expulso de novo. A correção é fazer o handshake do socket usar um token novo (ou um token curto específico para socket) e forçar um refresh antes de reconectar.
Então aparece um segundo padrão: usuários que deixam a página aberta são desconectados em quase exatamente 60 segundos. Isso aponta para um timeout de infraestrutura. O load balancer derruba conexões inativas, e o app não envia heartbeats. Um ping a cada 25 segundos mais um timeout sensato de inatividade para o proxy para de causar flapping.
Documente o que você mudou para que continue consertado: códigos de fechamento esperados, regras de token para sockets (de onde vem, quando renova, o que fazer em 401), configurações de heartbeat (intervalo de ping, timeout de pong) além de qualquer timeout de proxy, e regras de reconexão (timings de backoff, tentativas máximas, quando parar e mostrar um botão “Atualizar”).
Às vezes corrigir é mais lento que refatorar. Se seu handler de socket mistura conexão/auth, lógica de negócio, escrita no banco e checagens de permissão num só lugar, pequenas mudanças geram novas quebras. Separe responsabilidades: uma camada para conexão e auth, outra para eventos, outra para dados.
Se você está lidando com um protótipo gerado por IA que funcionou num demo mas continua quebrando em produção, FixMyMess (fixmymess.ai) pode ajudar a diagnosticar o fluxo de sockets, reparar autenticação e lógica de reconexão, e endurecer o app para tráfego real após uma auditoria de código gratuita.
Perguntas Frequentes
Por que meu recurso WebSocket funcionou num demo e quebrou depois do lançamento?
Porque produção adiciona estresse do mundo real e “coisas no meio do caminho”. Mais conexões simultâneas, proxies com timeouts de inatividade, ciclos de refresh de token, trocas de rede/suspensão de dispositivos móveis e múltiplas instâncias de servidor podem transformar um demo estável em desconexões e reconexões repetidas.
Qual é a informação mínima que devo logar para debugar um loop de desconexão?
Capture uma linha do tempo curta e precisa em torno de uma conexão: timestamps do cliente e do servidor, um identificador de usuário/sessão, um ID por conexão que você gera, o código de fechamento e a razão, e os últimos eventos antes da queda (open, auth, subscribe, ping/pong). Com isso você geralmente consegue dizer se o cliente desapareceu, o proxy cortou ou seu servidor fechou a conexão.
O que o código de fechamento WebSocket 1006 geralmente significa?
1006 é um fechamento anormal — o browser não recebeu um frame de fechamento limpo. Costuma apontar para quedas de rede, o app entrando em sleep, timeouts do proxy/load balancer ou um crash do servidor que terminou a conexão TCP sem um close WebSocket apropriado.
Como devo tratar fechamentos “não autorizados” sem reconectar indefinidamente?
Não trate como se fosse uma falha de rede intermitente. Pare o loop de reconexão, atualize a sessão ou o token primeiro e então abra um socket novo com as credenciais atualizadas. Se você continuar reconectando com o mesmo token expirado, criará um loop apertado que parece ser rede, mas é autenticação.
Qual timing de heartbeat (ping/pong) devo usar para evitar timeouts de inatividade?
Comece com um heartbeat a cada 15–25 segundos e considere a conexão “morta” após 2–3 respostas perdidas, então reconecte usando backoff. O objetivo é evitar que intermediários marquem a conexão como inativa, sem drenar bateria ou dados em mobile.
Como proxies ou load balancers causam desconexões aleatórias em WebSockets?
Qualquer coisa entre o browser e seu app pode afetar WebSockets: reverse proxies, CDNs e load balancers precisam suportar o upgrade HTTP, preservar cabeçalhos necessários e permitir conexões de longa duração. Um modo comum de falha é um timeout de inatividade em torno de 30–60 segundos que mata sockets “silenciosos” a menos que você envie heartbeats.
Preciso de sticky sessions para WebSockets em produção?
Se você mantém estado importante na memória (salas, presença, subscriptions), uma reconexão roteada para outra instância pode “conectar” mas não restaurar o estado, causando mensagens perdidas ou falhas. Ou torne servidores stateless movendo estado para armazenamento compartilhado/pub‑sub, ou garanta que a infraestrutura roteie consistentemente quando realmente precisar de sticky sessions.
Como paro mensagens duplicadas após reconexões?
Use IDs de evento ou números de sequência e torne ambos os lados tolerantes a retries. Na reconexão, o cliente deve retomar a partir do “último recebido” e o servidor deve processar eventos de forma idempotente para que replays não causem cobranças, criações ou envios duplicados.
Quando devo usar SSE ou polling em vez de WebSockets?
Se o cliente recebe principalmente atualizações e não precisa de interação bidirecional, SSE costuma ser mais simples e comporta‑se melhor através de muitos proxies porque é HTTP padrão. Para máxima confiabilidade, tenha um caminho de fallback controlado para que o app ainda funcione mesmo quando sockets falharem, mesmo que as atualizações cheguem mais devagar.
Quando devo contratar a FixMyMess para consertar meu recurso em tempo real?
Se seu app é um protótipo gerado por IA e você está preso em um loop de reconexão que não consegue explicar com os logs, normalmente é mais rápido obter um diagnóstico estruturado do que continuar ajustando timeouts. FixMyMess (fixmymess.ai) pode auditar o código, apontar se a causa é autenticação, infraestrutura ou escala, e reparar o fluxo em tempo real para que aguente tráfego real.