Boucles de déconnexions WebSocket : réparer les fonctionnalités temps réel après le lancement
Les boucles de déconnexions WebSocket peuvent ruiner les applis temps réel après le lancement. Apprenez à déboguer l'auth, les timeouts, la montée en charge et à ajouter des solutions de repli sûres.

Pourquoi les fonctions temps réel tombent en panne après le lancement
« Temps réel » signifie généralement que l'écran se met à jour sans actualisation. Les messages de chat apparaissent instantanément, la présence montre qui est en ligne, les tableaux de bord avancent, et les alertes s'affichent dès qu'il y a un changement.
Ces fonctionnalités peuvent sembler parfaites en démo puis se casser après le lancement parce que la production se comporte différemment. Plus d'utilisateurs veut dire plus de connexions simultanées. Un proxy ou un load balancer se place entre le navigateur et votre serveur. Les tokens expirent. Les téléphones changent de réseau, se mettent en veille puis se réveillent. Chacun de ces facteurs peut transformer une connexion stable en une boucle de déconnexions et reconnexions.
Pour l'utilisateur, une boucle de déconnexions WebSocket ressemble à « ça marche une seconde, puis ça casse ». Les messages arrivent en retard ou pas du tout. L'interface clignote « Reconnexion... » sans cesse. La présence vacille. Les tableaux de bord se figent puis sautent. Parfois des doublons apparaissent parce que le client renvoie après la reconnexion.
Ce qui change après le lancement
Quelques évolutions prévisibles poussent les sockets au bord : plus de connexions simultanées que ce que vous avez testé en local, des proxies qui ferment les connexions « inactives », une authentification WebSocket qui échoue pendant un rafraîchissement ou une rotation de token, des coupures réseau mobile qui déclenchent des reconnexions agressives, et plusieurs instances serveur sans état partagé (ou sans sessions sticky quand vous comptez sur l'état en mémoire).
La fiabilité des sockets n'est pas « ne jamais se déconnecter ». Les déconnexions arriveront. La fiabilité, c'est que l'application récupère vite et en sécurité, et qu'elle ne perd pas d'événements importants. Manquer un indicateur de saisie est acceptable. Manquer « message envoyé », le statut d'une commande ou l'état d'un paiement ne l'est pas.
Repérer le modèle avant de toucher quoi que ce soit
Quand le temps réel casse après le lancement, la façon la plus rapide de perdre du temps est de « corriger » le code à l'aveugle. Commencez par décrire la boucle si clairement que vous puissiez prédire la prochaine déconnexion.
Nommez le symptôme réel que vous observez. « C'est instable » cache des indices. « Reconnexion toutes les 6 secondes et notifications en double » est traçable.
Avant de modifier les réglages, répondez à quelques questions :
- Quels clients sont affectés (web, iOS, Android, un seul navigateur) ?
- Quel environnement (local, staging, production seulement) ?
- Ça touche tout le monde ou des comptes spécifiques ?
- Ça se produit aux heures de pointe ou juste après des déploiements ?
Ensuite, capturez des preuves pendant que ça se produit. Un instantané court vaut des heures de conjectures. Au minimum, collectez les horodatages (client et serveur), un ID utilisateur ou de session, un ID de connexion que vous générez par socket, le code de fermeture et la raison (si disponible), et les derniers événements avant la chute (connect, auth, subscribe, ping/pong).
Pour séparer les déconnexions côté serveur des chutes côté client, comparez les timelines. Si le client affiche le code 1006 (fermeture anormale) et que le serveur n'a pas enregistré de fermeture propre, suspectez le réseau, des timeouts proxy, ou l'application qui est passée en veille. Si le serveur ferme juste après « auth » ou « subscribe », suspectez votre logique (token invalide, permissions manquantes, exceptions levées).
Astuce pratique : reproduisez d'abord avec un utilisateur dans un seul onglet. Si vous ne pouvez pas, le déclencheur peut être lié à la charge.
Pas à pas : déboguer une boucle de déconnexion
Quand vous observez une boucle de déconnexion, résistez à l'envie de modifier d'abord les timeouts. Rendre la boucle visible : que s'est-il passé juste avant la chute, et qui programme la reconnexion.
Commencez par des logs simples autour du cycle de vie du socket. Vous voulez une histoire propre du début à la fin : connect, auth, subscription, flux de messages, puis raison de la fermeture. Incluez des horodatages et un petit ID de connexion pour que plusieurs onglets ne se mélangent pas.
Loggez l'essentiel dans l'ordre : connect démarré/open, auth envoyé et réussite/échec, subscribe envoyé et ack, close/error (code et raison), et reconnexion programmée (et par quoi).
Reproduisez ensuite avec la configuration la plus réduite : un utilisateur, un onglet, pas de jobs en arrière-plan. Une fois que ça échoue de façon fiable, ajoutez de la complexité pas à pas (deuxième onglet, deuxième utilisateur, plus de messages). Cela vous dira si le déclencheur est la charge, la concurrence, ou une subscription spécifique.
Ensuite, inspectez les codes de fermeture et les erreurs. Les fermetures de type politique pointent souvent vers l'auth ou des règles d'origine. Les timeouts pointent vers les heartbeats, les proxies, ou un serveur bloqué. Les fermetures anormales signifient souvent un crash ou un réseau qui a disparu sans fermeture propre.
Vérifiez aussi si vous avez deux mécanismes de reconnexion en même temps : votre logique plus celle de la librairie socket. Cela peut créer des tempêtes de reconnexion même quand le problème sous-jacent est mineur.
Enfin, testez depuis un réseau différent (hotspot mobile vs Wi‑Fi du bureau). Si ça arrive seulement sur un réseau, concentrez-vous sur les proxies, VPN, portails captifs ou timeouts d'inactivité agressifs.
Auth sur les sockets : où ça casse généralement
Beaucoup de bugs « réseau » sont en réalité des bugs d'auth. L'application se charge bien, les appels API fonctionnent, puis la fonctionnalité live reste bloquée en reconnexion.
Trois configurations d'auth courantes
La plupart des applis authentifient les sockets de trois façons : réutiliser une session cookie, envoyer un bearer token lors de la connexion, ou récupérer un token court à usage unique pour le socket via HTTPS d'abord. Toutes fonctionnent, mais chacune a un mode d'échec fréquent.
Un décalage classique : les requêtes HTTP normales sont authentifiées, mais la poignée de main WebSocket ne l'est pas. Les cookies peuvent être envoyés à votre API mais bloqués pour le socket à cause de règles cross-origin. Ou le serveur attend un header Authorization, mais la librairie client ne peut transmettre le token que via query param ou subprotocol.
Échecs courants après lancement :
- Les cookies ne sont pas inclus à cause de SameSite, Secure ou des réglages de domaine (fonctionne en localhost, casse sur le vrai domaine).
- Le socket se connecte avant que la session soit prête.
- Le socket conserve un access token périmé après un refresh et se fait éjecter en boucle.
- Le serveur ferme en « unauthorized » et le client reconnecte immédiatement, créant beaucoup de bruit.
Traiter les fermetures non autorisées sans reconnexions infinies
Traitez les échecs d'auth différemment des pannes réseau intermittentes. Si le serveur ferme avec un code ou un message lié à l'auth, arrêtez la boucle de reconnexion et récupérez d'abord la session. Rafraîchissez le token (ou demandez la reconnection), puis ouvrez une nouvelle socket avec les nouvelles informations.
Si vous devez retenter, utilisez un backoff (1s, 2s, 5s, 10s) et ajoutez du jitter pour éviter que de nombreux clients se reconnectent exactement au même instant.
Un scénario fréquent : un tableau de bord tourne pendant une heure, le token se renouvelle, mais le socket continue d'envoyer l'ancien token et se fait fermer toutes les quelques secondes. La solution n'est pas « plus de retries » : c'est redémarrer le socket quand le token change.
Heartbeats, timeouts et comportement de reconnexion
Beaucoup de boucles viennent de ceci : la connexion reste inactive et quelque chose au milieu la tue. Ce « quelque chose » peut être votre serveur, un proxy ou load balancer, un CDN, le Wi‑Fi d'un hôtel, ou un téléphone qui suspend l'activité en arrière-plan.
La solution habituelle est un heartbeat plus une logique de reconnexion raisonnable. Les heartbeats peuvent être ping/pong (idéal si votre librairie le gère) ou un petit keepalive applicatif. Dans tous les cas, vous voulez assez de trafic pour que les intermédiaires ne considèrent pas le socket comme inactif.
Soyez prudent sur les timings. Beaucoup de proxies ferment les connexions inactives autour de 30–60 secondes. Un point de départ courant est un heartbeat toutes les 15 à 25 secondes, et un timeout client après 2 à 3 heartbeats manqués. Trop agressif pompe la batterie et les données sur mobile. Trop lent meurt discrètement.
La logique de reconnexion est l'autre moitié. Les reconnexions instantanées peuvent créer une tempête, surtout après un déploiement ou une brève panne. Utilisez un backoff exponentiel avec jitter et un plafond, réinitialisez le backoff seulement après que la connexion soit restée saine pendant une fenêtre, et rendez la reconnexion idempotente : ré-authentifiez et vous ré-abonnez, mais ne créez pas de souscriptions doublons.
Les connexions « half-open » sont sournoises : le client croit être connecté alors que le serveur a disparu. Les timeouts de heartbeat permettent de le détecter rapidement.
Proxies et load balancers : causes cachées de déconnexions
Si le temps réel fonctionnait en local mais vacille en production, vérifiez le chemin réseau avant de réécrire le code socket. Reverse proxies, CDN et load balancers peuvent fermer des connexions inactives, faire tourner les instances, ou retirer des headers.
Ce que les proxies changent pour les WebSockets
Un WebSocket commence comme une requête HTTP puis passe en upgrade. Tout ce qui est devant votre appli doit supporter cet upgrade et garder la connexion ouverte. Beaucoup de configurations imposent aussi des timeouts d'inactivité ou un âge maximum de connexion. Si votre appli n'envoie des données que quand l'utilisateur clique, la connexion peut paraître inactive et être coupée.
Les sessions sticky sont un autre piège. Si vous gardez de l'état important en mémoire (subscriptions, appartenance aux rooms, contexte utilisateur) et que le load balancer route une reconnexion vers une autre instance, l'utilisateur « se connecte » mais rate des événements ou échoue les vérifications d'état. L'état partagé (Redis, base de données, message broker) réduit le besoin de stickiness.
Terminaison TLS et surprises avec les headers d'auth
Quand la terminaison TLS se fait au proxy, votre appli peut voir la requête comme HTTP sauf si les headers forwardés sont correctement configurés. Cela peut casser des vérifs comme « n'autoriser que les cookies sécurisés » ou des règles d'origine strictes. Certains proxies enlèvent ou renomment des headers, ce qui casse l'auth par token.
Pour confirmer ce qui ferme la connexion, comparez les codes de fermeture des deux côtés, cherchez dans les logs du proxy des messages comme « upstream timeout » ou « idle timeout », augmentez temporairement les timeouts d'inactivité pour voir si le problème disparaît, et vérifiez que l'upgrade et les headers forwardés atteignent bien l'application.
Mettre à l'échelle le temps réel sans perdre d'événements
Le temps réel marche souvent en staging parce qu'il y a un seul serveur. Après le lancement, une seconde instance apparaît (ou votre plateforme commence à bouger le trafic), et les messages disparaissent. Les broadcasts n'atteignent que les utilisateurs sur la même machine. Les rooms et la présence deviennent incohérentes. Les reconnexions peuvent atterrir sur un serveur qui ne connaît pas l'état du client.
La première règle : ne gardez pas l'état socket important uniquement en mémoire. Cela inclut l'état des subscriptions, les mappages user→socket, la présence, et le dernier ID d'événement reçu. L'état en mémoire disparaît au déploiement et diffère par serveur.
La plupart des applis finissent par utiliser un de ces modèles : pub/sub partagé pour que n'importe quel serveur puisse publier et tous puissent délivrer, un service temps réel dédié qui possède les connexions pendant que les serveurs API restent stateless, ou une queue pour les événements qu'il ne faut pas perdre afin de pouvoir les réessayer en toute sécurité.
Les reconnexions sont l'endroit où les doublons s'insinuent. Utilisez un ID d'événement (ou un numéro de séquence) par canal et demandez au client d'envoyer le « last received ». Côté serveur, rendez les handlers idempotents afin que traiter le même événement deux fois ne double pas une facture, ne crée pas deux enregistrements, et n'envoie pas deux notifications.
Les déploiements ont aussi besoin d'un plan. Si vous redémarrez des serveurs sans prévenir, vous forcez des reconnexions massives et des conditions de concurrence. Ajoutez une étape de drain : n'acceptez plus de nouvelles connexions sur l'ancienne instance, laissez les existantes se terminer, puis terminez.
Fallbacks qui maintiennent l'app utilisable
Le temps réel est génial… jusqu'à ce qu'il ne le soit plus. Quand les utilisateurs rencontrent des boucles de déconnexion, vous voulez deux choses : des sockets stables et une appli qui fonctionne encore quand les sockets ne le sont pas.
Les WebSockets excellent pour l'interaction bidirectionnelle (chat, multijoueur, curseurs en direct). Si le client reçoit surtout des mises à jour (changements d'état, notifications, tableaux de bord), les Server-Sent Events (SSE) peuvent être plus simples et plus fiables car ils utilisent une connexion HTTP standard et passent souvent mieux à travers les proxies.
Un repli pratique est une dégradation contrôlée : essayer WebSockets, passer à SSE si le socket n'arrive pas à s'ouvrir ou tombe trop souvent, basculer sur du short polling si nécessaire, et si la reconnexion échoue toujours, mettre l'app en mode limité (lecture seule ou « envoyer plus tard ») tout en réessayant en arrière-plan.
Gardez l'UI honnête. Affichez l'état de connexion (Connecté, Reconnexion, Hors ligne) et la dernière heure de mise à jour. Un bouton « Réessayer maintenant » aide quand quelqu'un vient de changer de réseau.
Côté serveur, concevez les streams pour qu'un client puisse reprendre après une reconnexion. Envoyez les événements avec des IDs ou des timestamps et permettez « tout depuis X ». Pour SSE, utilisez Last-Event-ID. Pour WebSockets, utilisez un curseur de reprise ou un token à la connexion.
Erreurs courantes qui rendent les sockets fragiles
Toutes les déconnexions ne sont pas des bugs. Les réseaux mobiles coupent. Les ordinateurs portables se mettent en veille. Les navigateurs pause les onglets en arrière-plan. La fragilité apparaît quand votre appli traite des déconnexions normales comme des urgences et réessaie si agressivement qu'elle crée une panne auto-infligée.
Une erreur de sécurité évitable est de mettre des secrets dans l'URL. Les query strings sont copiées dans les logs, analytics, rapports d'erreur et captures d'écran. Si votre token socket est dans l'URL, assumez qu'il fuira. Il est aussi facile de logger des tokens par accident lors du débogage de la poignée de main.
Le développement local peut vous induire en erreur. En localhost il n'y a pas de proxy d'entreprise, pas de load balancer, et pas de politique de timeout d'inactivité. En production, un proxy peut fermer des connexions inactives, retirer des headers, ou bloquer les requêtes d'upgrade.
Quelques patterns qui rendent les sockets fragiles :
- Boucles de retry sans backoff ni jitter.
- Auth dans les query strings, ou logs qui capturent des tokens.
- Pas de limites serveur sur les tentatives de reconnexion par user/IP.
- Logique de reconnexion qui se réabonne aveuglément et crée des listeners en double.
- Ne pas tester derrière un proxy ou un load balancer.
Les subscriptions en double sont particulièrement sournoises. Après une reconnexion, le client peut rejoindre la même room ou enregistrer le même handler alors que le serveur n'a pas nettoyé l'ancien. Corrigez cela en rendant les subscriptions idempotentes par connexion et en suivant des connection IDs pour qu'un nouveau socket remplace proprement l'ancien.
Vérifications rapides avant de déployer la correction
Avant de déployer un changement WebSocket, passez rapidement le client, le serveur et l'infrastructure. La plupart des boucles ne viennent pas d'un seul bug. Ce sont deux ou trois petits problèmes qui apparaissent ensemble.
Vérifications côté client
Votre client doit rester calme en cas d'échec. Utilisez un backoff de reconnexion avec jitter et un plafond, affichez l'état de connexion dans l'UI, ajoutez une logique de reprise (dernier ID ou version) pour que les courtes pertes ne fassent pas perdre de données, dédupliquez les événements pour que les reconnexions n'appliquent pas deux fois les mêmes mises à jour, et fermez proprement les sockets à la déconnexion ou au changement de compte.
Vérifications côté serveur et infrastructure
Rendez les déconnexions compréhensibles. Si le serveur ferme une connexion, il doit le faire pour une raison claire, et une ligne de log doit l'expliquer. Utilisez des codes de fermeture explicites, appliquez l'auth à la connexion et aux messages sensibles, configurez heartbeats et timeouts pour que les clients sains ne soient pas expulsés, définissez des limites de connexion par user/IP, et confirmez les réglages WebSocket du proxy/load balancer (timeouts d'inactivité, besoin éventuel de stickiness).
Règle simple : si vous ne pouvez pas expliquer une déconnexion avec une seule ligne de log, vous n'êtes pas prêt à déployer.
Tests rapides qui attrapent des régressions
Exécutez quelques scénarios qui reproduisent souvent des boucles : un utilisateur avec de nombreux onglets en se connectant/déconnectant, beaucoup d'utilisateurs qui se connectent en même temps (même un petit test de charge), déployer pendant que des utilisateurs sont connectés et observer le comportement de reconnexion, et simuler des réseaux instables (basculer Wi‑Fi/cellulaire, mettre en veille un portable) pour confirmer que l'app récupère.
Ce à quoi ressemble « fini » : les connexions tiennent pendant des périodes prévisibles, les reconnexions ralentissent au lieu de s'accélérer, et quand il y a une chute vous pouvez pointer une cause claire.
Un exemple réaliste et les étapes suivantes
Un fondateur lance un tableau de bord de ventes en direct. En staging tout fonctionne. Le jour du lancement, les tickets support arrivent : la page affiche « Reconnexion... » toutes les quelques secondes, et certains utilisateurs ne reçoivent jamais les mises à jour en direct.
Le premier indice est dans les logs serveur : beaucoup de connexions se terminent juste après l'expiration d'un access token. Côté client, l'app reconnecte vite mais réutilise le même token expiré, donc elle se fait expulser à nouveau. La correction consiste à faire la handshake socket avec un token frais (ou un token socket court) et forcer un rafraîchissement avant la reconnexion.
Puis un deuxième motif apparaît : les utilisateurs qui laissent la page ouverte sont déconnectés à presque exactement 60 secondes. Cela pointe vers un timeout d'infrastructure. Le load balancer coupe les connexions inactives et l'app n'envoie pas de heartbeats. Un ping toutes les 25 secondes plus un timeout raisonnable arrête le basculement.
Documentez ce que vous avez changé pour que ce soit durable : codes de fermeture attendus, règles de token pour les sockets (d'où vient-il, quand il se rafraîchit, que fait-on sur 401), paramètres de heartbeat (intervalle de ping, timeout de pong) ainsi que tout timeout proxy, et règles de reconnexion (timings de backoff, tentatives max, quand arrêter et afficher un bouton « Actualiser »).
Parfois un correctif est plus lent qu'une refactorisation. Si votre handler socket mélange connexion/auth, logique métier, écritures en base et vérifications de permission en un seul endroit, de petits changements créent de nouvelles cassures. Séparez les responsabilités : une couche pour la connexion et l'auth, une pour les événements, une pour les données.
Si vous travaillez avec un prototype généré par IA qui fonctionnait en démo mais tombe en panne en production, FixMyMess (fixmymess.ai) peut aider à diagnostiquer le flux socket, réparer l'auth et la logique de reconnexion, et durcir l'app pour du trafic réel après un audit de code gratuit.
Questions Fréquentes
Pourquoi ma fonctionnalité temps réel WebSocket fonctionnait en démo mais a cassé après le lancement ?
Parce que la production ajoute des contraintes et des « éléments entre les deux ». Plus de connexions simultanées, des proxies avec des timeouts d'inactivité, des cycles de rafraîchissement de token, des changements de réseau ou de mise en veille sur mobile, et plusieurs instances serveur peuvent transformer une démo stable en une suite de déconnexions/reconnexions.
Quelle est l'information minimale à logger pour déboguer une boucle de déconnexion ?
Capturez une timeline serrée autour d'une connexion : horodatages client et serveur, un identifiant utilisateur/session, un ID par connexion que vous générez, le code de fermeture et la raison, et les derniers événements avant la chute (open, auth, subscribe, ping/pong). Avec ça, vous pouvez souvent déterminer si le client a disparu, si le proxy a coupé, ou si c'est votre serveur qui a clos.
Que signifie généralement le code de fermeture WebSocket 1006 ?
Un 1006 est une fermeture anormale : le navigateur n'a pas reçu de trame de fermeture propre. Cela pointe souvent vers une coupure réseau, une mise en veille de l'appareil, des timeouts proxy/load balancer, ou un crash serveur qui a fermé la connexion TCP sans fermer proprement le WebSocket.
Comment gérer les fermetures « unauthorized » sur les sockets sans reconnexions infinies ?
Ne le traitez pas comme une panne réseau passagère. Arrêtez la boucle de reconnexion, rafraîchissez d'abord la session ou le token, puis ouvrez une nouvelle socket avec des identifiants valides. Reconnecter sans mettre à jour le token expiré crée une boucle serrée qui ressemble à un bug réseau mais qui est en réalité un problème d'auth.
Quel timing de heartbeat (ping/pong) devrais-je utiliser pour éviter les timeouts d'inactivité ?
Commencez avec un heartbeat toutes les 15–25 secondes et considérez la connexion « morte » après 2–3 réponses manquées, puis reconnectez avec backoff. L'objectif est d'empêcher les intermédiaires de considérer le socket comme inactif, sans pour autant user la batterie ou les données sur mobile.
Comment les proxies ou load balancers provoquent-ils des déconnexions WebSocket aléatoires ?
Tout ce qui se place entre le navigateur et votre appli peut affecter les WebSockets : reverse proxies, CDN, load balancers doivent supporter l'upgrade HTTP, conserver les headers nécessaires et autoriser des connexions longues. Un échec courant est un timeout d'inactivité vers 30–60 secondes qui tue les sockets « calmes » si vous n'envoyez pas de heartbeats.
Ai-je besoin de sessions sticky pour les WebSockets en production ?
Si vous stockez de l'état important en mémoire (rooms, présence, subscriptions), une reconnexion dirigée vers une autre instance peut « connecter » l'utilisateur mais ne pas restaurer l'état, provoquant des messages manquants ou des échecs de vérification. Soit rendez les serveurs stateless en déplaçant l'état vers un stockage/shared pub-sub, soit assurez la tokenisation/stickiness si vous avez vraiment besoin de stickiness.
Comment arrêter les messages dupliqués après des reconnexions ?
Utilisez des IDs d'événement ou des numéros de séquence et rendez les deux côtés tolérants aux retries. À la reconnexion, le client doit reprendre depuis le « dernier reçu », et le serveur doit traiter les événements de façon idempotente pour qu'un replay ne créé ni double facturation, ni doublons d'enregistrements, ni envois répétés.
Quand devrais-je utiliser SSE ou du polling à la place des WebSockets ?
Si le client reçoit surtout des mises à jour et n'a pas besoin d'interaction bidirectionnelle, SSE est souvent plus simple et se comporte mieux à travers certains proxies car c'est une connexion HTTP standard. Pour la meilleure fiabilité, prévoyez un mécanisme de repli contrôlé afin que l'application reste utilisable même si les sockets flanchent, même si les mises à jour arrivent un peu plus lentement.
Quand devrais-je faire appel à FixMyMess pour réparer ma fonctionnalité temps réel ?
Si votre application est un prototype généré par IA et que vous êtes coincé dans une boucle de reconnexion inexplicable d'après les logs, il est souvent plus rapide d'obtenir un diagnostic structuré que d'ajuster les timeouts à l'aveugle. FixMyMess (fixmymess.ai) peut auditer le code, identifier si la cause est l'auth, l'infrastructure ou la montée en charge, puis réparer le flux temps réel pour qu'il résiste au trafic réel.