Ubuntu 24.04 « Connection reset by peer » : prouver si c’est le client, le proxy ou le serveur (cas n°14)

Cet article vous a aidé ?

« Connection reset by peer » est l’équivalent réseau d’un haussement d’épaules. Quelque chose, quelque part, a claqué la porte au milieu d’une conversation. Votre application signale une erreur. Le métier signale une panne. Et trois équipes commencent immédiatement un vif jeu du « c’est pas moi ».

Ce dossier traite de la fin de ce jeu. Sur Ubuntu 24.04, vous pouvez généralement prouver qui a envoyé le reset (client, proxy/load balancer ou serveur) avec une poignée de captures, compteurs et journaux — si vous collectez les bonnes preuves dans le bon ordre.

Ce que « connection reset by peer » signifie réellement (et ce que ça ne signifie pas)

Au niveau du câble, un « reset » est TCP qui dit : « Stop. On n’y va pas. » C’est un segment RST. Ce n’est pas un timeout, pas une perte de paquets (même si une perte peut y mener), et pas nécessairement « le serveur a planté ». C’est une fermeture explicite qui interrompt immédiatement une connexion.

Les applications voient cela comme :

  • ECONNRESET (courant dans Node.js, Go, Python, Java)
  • recv() failed (104: Connection reset by peer) (courant dans les journaux Nginx)
  • Connection reset by peer depuis curl/OpenSSL quand le pair interrompt pendant HTTP/TLS

Nuance importante : « peer » signifie « l’autre côté du point de vue de l’application ». Si votre client parle à un reverse proxy, le « peer » est le proxy. Si votre proxy parle à un upstream, le « peer » est l’upstream. Si un pare-feu injecte un reset, le « peer » est effectivement « celui qui a falsifié le reset », d’où l’importance de capturer les paquets pour identifier l’émetteur.

Les resets TCP apparaissent pour quelques raisons légitimes :

  • Le processus a fermé un socket brutalement (ou l’OS l’a fait pour le processus).
  • L’autre côté a reçu des données pour une connexion qu’il ne reconnaît pas (pas d’état), donc il envoie un RST.
  • Un middlebox (proxy, pare-feu, NAT) décide que votre flux est indésirable et envoie un RST.
  • Un timeout ou une exhaustion de ressources fait qu’un composant perd l’état ; des paquets ultérieurs déclenchent un RST.

Et voici la conclusion pratique : le chemin le plus rapide vers la vérité est de trouver l’équipement qui a envoyé le RST. Ce n’est pas de la philosophie. C’est du tcpdump.

Playbook de diagnostic rapide (premier/deuxième/troisième)

Quand vous êtes de garde, vous ne voulez pas un séminaire réseau. Vous voulez une séquence qui réduit la zone impactée en quelques minutes.

Premier : classer le reset (face client ou face upstream)

  • Si les utilisateurs atteignent un proxy/LB, vérifiez si les logs du proxy indiquent un client reset ou un upstream reset.
  • Si le serveur d’application est exposé directement, vérifiez les journaux du serveur et les stats socket pour les resets.

Deuxième : capturer le RST au saut contrôlé le plus proche

  • Capturez sur l’interface du proxy/LB si elle existe ; il est entre les mondes.
  • Si pas de proxy, capturez sur la NIC du serveur.
  • Si vous contrôlez le client (sonde synthétique ou bastion), capturez aussi là-bas.

Troisième : corréler avec l’heure, la tuple et la direction

  • Faites correspondre la 5-tuple (IP src, port src, IP dst, port dst, protocole).
  • Identifiez qui envoie le RST en regardant l’IP/MAC source du paquet et le point de capture.
  • Confirmez la raison du composant avec les journaux/compteurs (journaux applicatifs, logs proxy, compteurs kernel, conntrack, erreurs TLS).

Si vous ne retenez qu’une chose : ne discutez pas de la culpabilité tant que vous ne pouvez pas montrer un frame RST et dire « cette machine l’a envoyé ».

Faits et contexte qui changent comment vous déboguez

Ce ne sont pas des trivia. Chacun change quelles preuves vous croyez et quelles hypothèses vous priorisez.

  1. Le RST TCP précède la plupart de vos outils. Il fait partie du comportement TCP de base depuis les RFC ; votre service mesh dernier cri n’est qu’un nouvel acteur qui envoie d’anciens paquets.
  2. Linux enverra un RST s’il n’y a aucun socket en écoute. Si rien n’est lié au port de destination, le kernel répond par RST à un SYN, que les clients interprètent souvent comme un « reset ».
  3. RST n’est pas « toujours le serveur ». Pare-feux, load balancers, passerelles NAT et IDS/IPS peuvent générer des resets. Les middleboxes le font pour échouer en mode fermé — ou de façon créative.
  4. Le désalignement des keep-alives est un générateur classique de resets. Une partie réutilise une connexion inactivée après que l’autre côté a déjà expiré l’état. La première nouvelle requête reçoit un reset.
  5. L’expiration d’état NAT provoque des « resets fantômes ». Si un appareil NAT oublie une entrée tandis que les endpoints continuent, le paquet suivant peut déclencher un reset ou être perdu — dans les deux cas, cela ressemble à de la instabilité aléatoire.
  6. Les problèmes de Path MTU peuvent se faire passer pour des resets. Techniquement c’est plus souvent des blocages que des resets, mais certaines piles et middleboxes réagissent mal et interrompent les connexions pendant TLS ou HTTP/2.
  7. Ubuntu 24.04 fournit nftables dans l’outillage par défaut. iptables peut encore exister comme couche de compatibilité, mais vous devez savoir si les règles sont dans nft, pas dans votre mémoire.
  8. QUIC/HTTP/3 a déplacé de nombreux modes d’échec vers UDP. Votre navigateur peut « réparer » un chemin TCP cassé en changeant de protocole, rendant le reset intermittent et dépendant de l’agent utilisateur.
  9. L’observabilité a changé la dynamique sociale. Il y a dix ans, les équipes débataient sur « j’ai l’impression que ». Aujourd’hui vous pouvez — et devriez — débattre à partir de captures de paquets et de journaux structurés.

Une idée paraphrasée, parce que c’est encore le cadre le plus utile en exploitation : paraphrased idea« L’espoir n’est pas une stratégie. » (attribué dans la culture ops à Edsger W. Dijkstra)

Prouver qui l’a fait : client vs proxy vs serveur

Pensez en couches de garde. Le reset trouve son origine quelque part, traverse des sauts, puis devient « reset du pair » pour l’application qui le reçoit. Votre travail est d’identifier l’initiateur, pas la victime.

Cas A : le client a envoyé le reset (ça arrive plus souvent qu’on l’admet)

Les resets d’origine client apparaissent généralement comme :

  • Le navigateur a quitté la page ou fermé un onglet en plein milieu d’une requête.
  • L’application mobile a été mise en arrière-plan ; l’OS a tué des sockets pour économiser la batterie.
  • Timeout côté client plus court que le timeout serveur/proxy ; le client abandonne prématurément.
  • Une bibliothèque de retry annule agressivement la requête en vol.

Comment le prouver :

  • Sur la capture côté serveur ou proxy, vous voyez un RST provenant de l’IP client.
  • Les journaux serveur indiquent « client a fermé prématurément la connexion ».
  • Le proxy affiche 499 (Nginx) ou un état de terminaison cohérent avec un abandon côté client.

Cas B : le proxy/load balancer a envoyé le reset

Les proxies réinitialisent des connexions pour des raisons qui paraissent sensées sur le papier mais font mal en production :

  • Timeout d’inactivité atteint ; le proxy récupère les ressources.
  • Nombre maximal de requêtes par connexion atteint ; le proxy ferme sans vidage gracieux.
  • Logique de health-check ou circuit-breaker considère l’upstream comme mauvais et échoue vite.
  • Limites de buffer, limites d’en-têtes ou politiques de corps de requête déclenchent un abandon.
  • Les boîtes d’inspection TLS jugent votre certificat « étrange ».

Comment le prouver :

  • Sur la capture client, l’IP source du RST est le VIP ou l’IP du nœud du proxy/LB.
  • Sur la capture proxy, vous ne voyez pas de RST upstream — seulement le proxy générant le RST côté client.
  • Les logs du proxy montrent une erreur locale (timeout, limite dépassée, upstream indisponible), pas un abort upstream.

Cas C : le serveur a envoyé le reset

Les serveurs réinitialisent les connexions pour trois grandes catégories de raisons :

  • Comportement applicatif : plantage du processus, panic, sortie, fermeture abrupte des sockets, ou rejet de connexions pour logique de surcharge.
  • Comportement du kernel : pas d’écoute, overflow du backlog SYN, pression mémoire, ou règles de pare-feu qui rejettent avec tcp-reset.
  • Comportement d’infrastructure : exhaustion de conntrack, exhaustion de ports éphémères sur le serveur agissant comme client, ou changements de routage.

Comment le prouver :

  • La capture proche du serveur montre un RST provenant de l’IP du serveur.
  • Les journaux serveur coïncident avec des redémarrages, des kills OOM, ou des messages d’abandon applicatif.
  • Les compteurs kernel montrent des drops d’écoute ou des aborts qui augmentent pendant la fenêtre d’incident.

Blague #1 : Un reset TCP, c’est comme se faire raccrocher au milieu d’une phrase — soudain vous ne débattez plus d’architecture, vous débattez de qui a claqué le téléphone.

Tâches sur le terrain : commandes, sorties et décisions (12+)

Voici les tâches que j’exécute réellement sur des systèmes Ubuntu 24.04. Chacune inclut : commande, extrait réaliste de sortie, ce que ça signifie, et la décision suivante. Exécutez-les depuis le point le plus proche du symptôme : client, proxy ou serveur.

Task 1: Reproduce with curl and force a single connection

cr0x@server:~$ curl -vk --http1.1 --no-keepalive https://api.internal.example/v1/ping
*   Trying 10.20.30.40:443...
* Connected to api.internal.example (10.20.30.40) port 443 (#0)
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* Recv failure: Connection reset by peer
* OpenSSL SSL_read: Connection reset by peer, errno 104
curl: (56) Recv failure: Connection reset by peer

Sens : Le reset s’est produit pendant la poignée de main TLS. Cela réduit les suspects : terminator TLS (proxy/LB), IDS, stack TLS du serveur mal configurée, ou problèmes de chemin provoquant des middleboxes à interrompre.

Décision : Capturer des paquets sur le terminator TLS (proxy/LB) en premier. S’il n’y a pas de proxy, capturer sur la NIC du serveur.

Task 2: Identify whether you’re talking to a proxy/LB VIP

cr0x@server:~$ getent ahostsv4 api.internal.example
10.20.30.40     STREAM api.internal.example
10.20.30.40     DGRAM  api.internal.example
10.20.30.40     RAW    api.internal.example

Sens : IP unique. Ça peut toujours être un VIP.

Décision : Vérifier ARP/neigh pour voir si ça ressemble à un VIP (les patterns de vendor MAC aident) et demander « où se termine TLS ? »

Task 3: Confirm routing path and next hop (server side)

cr0x@server:~$ ip route get 10.20.30.40
10.20.30.40 via 10.10.0.1 dev ens5 src 10.10.2.15 uid 1000
    cache

Sens : Le trafic sort via la passerelle 10.10.0.1.

Décision : Si vous suspectez un pare-feu/NAT, cette passerelle est candidate. Capturez des deux côtés si possible.

Task 4: Baseline socket state and reset counters with ss

cr0x@server:~$ ss -s
Total: 1234 (kernel 0)
TCP:   642 (estab 120, closed 410, orphaned 0, timewait 350)
Transport Total     IP        IPv6
RAW       0         0         0
UDP       18        12        6
TCP       232       180       52
INET      250       192       58
FRAG      0         0         0

Sens : Beaucoup de TIME-WAIT et de sockets fermés n’est pas automatiquement mauvais, mais c’est un indice si ça monte en flèche pendant les resets.

Décision : Si TIME-WAIT explose sous charge, vérifier les déconnexions agressives côté client, keep-alives courts ou mauvais réutilisation des connexions.

Task 5: Inspect kernel TCP statistics for aborts and listen drops

cr0x@server:~$ nstat -az | egrep 'TcpExtListen|TcpAbort|TcpTimeout|TcpRetrans'
TcpExtListenDrops                  18                 0.0
TcpExtListenOverflows              5                  0.0
TcpAbortOnData                     22                 0.0
TcpAbortOnTimeout                  9                  0.0
TcpTimeouts                        133                0.0
TcpRetransSegs                     420                0.0

Sens : Les overflows/drops d’écoute suggèrent que le serveur n’a pas pu accepter les connexions assez vite (backlog SYN / pression sur la file d’accept). Les compteurs d’abort impliquent la pile locale qui a aborté des connexions.

Décision : Si ceux-ci augmentent durant l’incident, arrêtez de blâmer le client. Corrigez le backlog d’accept, la boucle d’accept de l’app, la saturation CPU, ou les rafales de connexions côté proxy.

Task 6: Validate the server is actually listening (and on what)

cr0x@server:~$ sudo ss -ltnp '( sport = :443 )'
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      4096   0.0.0.0:443       0.0.0.0:*    users:(("nginx",pid=1421,fd=6))

Sens : Quelque chose écoute sur le port 443, profondeur de file 4096 configurée.

Décision : Si rien n’écoute, le kernel RST les SYN. C’est votre réponse. S’il y a un écoute, continuez l’investigation.

Task 7: Packet capture to identify who sends the RST (server vantage)

cr0x@server:~$ sudo tcpdump -ni ens5 'tcp port 443 and (tcp[tcpflags] & (tcp-rst) != 0)' -vv
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:22:10.118322 IP 10.20.30.40.443 > 10.10.2.15.51544: Flags [R.], seq 20394822, ack 129388, win 0, length 0

Sens : Au serveur, on voit un paquet provenant de 10.20.30.40:443 envoyant un RST au client (10.10.2.15). Si 10.20.30.40 est un VIP de proxy, cela implique le proxy. Si c’est l’IP du serveur, cela implique le serveur.

Décision : Capturez sur le nœud proxy lui-même. Si le proxy ne l’a pas généré, vous le verrez entrant depuis l’upstream. S’il l’a généré, vous ne le verrez pas.

Task 8: Capture on the proxy to separate “upstream reset” vs “proxy reset”

cr0x@server:~$ sudo tcpdump -ni ens5 'host 10.10.2.15 and tcp port 443' -vv
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:22:10.118319 IP 10.10.2.15.51544 > 10.20.30.40.443: Flags [S], seq 129387, win 64240, options [mss 1460,sackOK,TS val 332191 ecr 0,nop,wscale 7], length 0
14:22:10.118322 IP 10.20.30.40.443 > 10.10.2.15.51544: Flags [R.], seq 0, ack 129388, win 0, length 0

Sens : RST immédiat au SYN (seq 0, ack SYN+1) depuis 10.20.30.40 suggère « rien n’écoute » sur ce VIP/port du point de vue du proxy ou une règle firewall locale qui rejette.

Décision : Vérifier le listener sur l’hôte/conteneur proxy et les règles de pare-feu. Si le proxy doit terminer TLS, vérifier que le service est en place et lié.

Task 9: Check Nginx error/access logs for client abort vs upstream abort

cr0x@server:~$ sudo tail -n 5 /var/log/nginx/error.log
2025/12/30 14:22:10 [error] 1421#1421: *441 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 10.10.2.15, server: api.internal.example, request: "GET /v1/ping HTTP/1.1", upstream: "https://10.30.40.50:8443/v1/ping", host: "api.internal.example"

Sens : Nginx (proxy) indique que l’upstream a réinitialisé la connexion tandis que Nginx lisait la réponse de l’upstream.

Décision : Arrêtez d’accuser le client. Déplacez l’investigation vers l’upstream 10.30.40.50:8443, et capturez là-bas.

Task 10: Check HAProxy termination state (if you use it)

cr0x@server:~$ sudo tail -n 3 /var/log/haproxy.log
Dec 30 14:22:10 lb1 haproxy[2011]: 10.10.2.15:51544 [30/Dec/2025:14:22:10.118] fe_https be_api/srv2 0/0/1/2/3 200 512 - - ---- 12/12/0/0/0 0/0 "GET /v1/ping HTTP/1.1"
Dec 30 14:22:10 lb1 haproxy[2011]: 10.10.2.15:51545 [30/Dec/2025:14:22:10.221] fe_https be_api/srv2 0/0/0/0/0 0 0 - - SD-- 3/3/0/0/0 0/0 "GET /v1/ping HTTP/1.1"

Sens : Les flags de terminaison (ici « SD– ») suggèrent que le côté serveur a été fermé. Les codes exacts varient selon la config, mais le journal vous dit quel côté a mis fin à la session.

Décision : Si la terminaison côté serveur domine pendant la fenêtre d’incident, concentrez-vous sur la stabilité de l’upstream, les timeouts et la pression sur les ressources.

Task 11: Check for conntrack exhaustion (middlebox-ish symptom on Linux NATs)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 262041
net.netfilter.nf_conntrack_max = 262144

Sens : Vous êtes pratiquement à court d’entrées conntrack. À ce stade, les nouvelles connexions sont droppées ou mal gérées. Certains environnements afficheront des resets ; d’autres des timeouts.

Décision : Augmenter nf_conntrack_max (en conscience de la mémoire), raccourcir les timeouts conntrack quand c’est sûr, et réduire le churn de connexion (keep-alive, pooling).

Task 12: Check nftables rules for “reject with tcp reset”

cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    iif "lo" accept
    tcp dport 22 accept
    tcp dport 443 reject with tcp reset
  }
}

Sens : Le pare-feu hôte envoie explicitement des TCP resets sur le port 443. Cela se manifestera comme « connection reset by peer » pour les clients.

Décision : Corriger la règle. Remplacer reject par accept pour les sources autorisées, ou utiliser un drop contrôlé si vous préférez des timeouts (mais préparez-vous à des échecs lents).

Task 13: Identify MTU and offload mismatches (quietly nasty)

cr0x@server:~$ ip link show dev ens5
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 02:ab:cd:ef:12:34 brd ff:ff:ff:ff:ff:ff

Sens : MTU est 1500 sur cette interface. Si d’autres segments utilisent des jumbo frames ou que des tunnels réduisent la MTU, des problèmes de fragmentation/PMTUD peuvent apparaître pendant TLS.

Décision : Si vous suspectez la MTU, lancez un ping ciblé avec DF entre les mêmes endpoints et ajustez MTU/MSS clamping sur le bon équipement.

Task 14: Prove PMTUD works (or doesn’t) with DF pings

cr0x@server:~$ ping -M do -s 1472 10.20.30.40 -c 3
PING 10.20.30.40 (10.20.30.40) 1472(1500) bytes of data.
ping: local error: message too long, mtu=1450
ping: local error: message too long, mtu=1450
ping: local error: message too long, mtu=1450

--- 10.20.30.40 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2040ms

Sens : Votre PMTU effective est 1450 (commun avec VXLAN/GRE/encapsulation cloud). Si votre stack/proxy ne clamp pas correctement le MSS, vous pouvez observer des échecs bizarres de handshake et parfois des resets.

Décision : Clamp le MSS sur la bordure (ou définir les MTU appropriées sur les interfaces), puis retester TLS.

Task 15: Check systemd journal around the exact failure time

cr0x@server:~$ sudo journalctl -u nginx --since "2025-12-30 14:20:00" --until "2025-12-30 14:25:00" | tail -n 8
Dec 30 14:22:09 api1 nginx[1421]: 2025/12/30 14:22:09 [warn] 1421#1421: *439 upstream timed out (110: Connection timed out) while reading response header from upstream
Dec 30 14:22:10 api1 nginx[1421]: 2025/12/30 14:22:10 [error] 1421#1421: *441 recv() failed (104: Connection reset by peer) while reading response header from upstream
Dec 30 14:22:11 api1 systemd[1]: nginx.service: Main process exited, code=killed, status=9/KILL
Dec 30 14:22:11 api1 systemd[1]: nginx.service: Failed with result 'signal'.
Dec 30 14:22:12 api1 systemd[1]: nginx.service: Scheduled restart job, restart counter is at 1.

Sens : Maintenant nous avons une narration : timeouts upstream, puis resets, puis Nginx se fait tuer et redémarre. Ce n’est pas un « souci réseau aléatoire ». Quelque chose tue Nginx (OOM ? déploiement ? watchdog ?).

Décision : Vérifier dmesg pour OOM et l’automatisation de déploiement. Si Nginx redémarre, il peut réinitialiser les connexions en vol.

Task 16: Check for OOM kills and kernel complaints

cr0x@server:~$ dmesg -T | tail -n 8
[Mon Dec 30 14:22:11 2025] Out of memory: Killed process 1421 (nginx) total-vm:512000kB, anon-rss:210000kB, file-rss:12000kB, shmem-rss:0kB, UID:0 pgtables:900kB oom_score_adj:0
[Mon Dec 30 14:22:11 2025] oom_reaper: reaped process 1421 (nginx), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Sens : Le kernel a tué Nginx. Cela génèrera absolument des resets et des états semi-ouverts étranges.

Décision : Corriger la pression mémoire (limites, fuites, tamponnement de logs, caches trop grands). Jusqu’à alors, chasser « le réseau » est un hobby, pas une réponse d’incident.

Blague #2 : Si vous devinez la cause sans capture de paquets, vous faites essentiellement de la réponse d’incident par astrologie.

Trois mini-récits d’entreprise (anonymisés, plausibles, techniquement exacts)

1) L’incident causé par une fausse hypothèse : « Le serveur nous a reset »

Le symptôme était clair : des clients mobiles dans une région spécifique recevaient « connection reset by peer » pendant la connexion. L’équipe API ne voyait rien d’évident. Les tableaux de bord du load balancer semblaient calmes. Quelqu’un a prononcé la phrase dangereuse : « Ça doit être l’app ; c’est la seule chose qui a changé la semaine dernière. »

Ils ont donc rollbacké l’app. Aucun changement. Ils ont rollbacké une migration de base. Aucun changement. Ils ont commencé à planifier un basculement régional. Pendant ce temps, le support client a appris de nouvelles et créatives façons de dire « merci de réessayer ».

Finalement, un SRE a fait la chose peu glamour : tcpdump sur les nœuds du load balancer, filtré pour RST. Les paquets RST ne venaient pas des serveurs API du tout. Ils venaient d’un appliance de sécurité en amont du load balancer, sourcé avec l’IP de l’appliance, et seulement pour des plages ASN de certains opérateurs mobiles.

La cause racine était une mise à jour de threat-intel qui a mal étiqueté un bloc NAT d’un opérateur mobile partagé comme hostile. L’appliance a commencé à injecter des resets pour des handshakes TLS correspondant à une heuristique. Le « peer » dans les logs client était le VIP du load balancer, mais l’émetteur n’était ni le LB ni le serveur — c’était la boîte que personne ne voulait admettre exister.

La correction fut simple et politique : whitelist, puis modifier le processus de contrôle de changement pour que les mises à jour de sécurité soient traitées comme des déploiements en production avec rollback et monitoring. Le grand enseignement : les hypothèses coûtent cher ; les paquets sont peu chers.

2) L’optimisation qui a échoué : timeouts inactifs agressifs

Une équipe plateforme voulait réduire le nombre de connexions sur une passerelle API interne. Ils ont réduit fortement le « idle timeout ». Les graphes semblaient excellents : moins de connexions ouvertes, moins de mémoire, CPU légèrement amélioré. Les gens se sont félicités en réunion. C’était leur postmortem en tête.

Une semaine plus tard, les resets ont augmenté — mais seulement pour une poignée de services. Ces services utilisaient HTTP keep-alive avec des connexions longues et des rafales peu fréquentes. Les clients étaient bien conçus, réutilisant les connexions. La passerelle les timeoutait désormais pendant les moments calmes.

Que s’est-il passé ? Le client envoyait la requête suivante sur une connexion qu’il croyait vivante. La passerelle avait déjà supprimé l’état et l’avait récupérée. La passerelle a répondu par un RST (ou parfois le kernel l’a fait, selon comment le socket a été fermé). Du point de vue client, « peer reset ». Du point de vue de la passerelle, « je vous ai dit d’arrêter d’être inactif ».

Le pire : les retries ont amplifié le bruit. Certains clients retentaient immédiatement, multipliant la charge. Le changement destiné à réduire l’utilisation des ressources a fini par augmenter le churn et la latence et ajouter des pics d’échecs imprévisibles.

La correction fut d’aligner les timeouts de bout en bout : idle keep-alive client, idle gateway, idle upstream, plus du jitter. Ils ont aussi ajouté un vidage gracieux des connexions pour les déploiements. L’optimisation, c’est bien. L’optimisation sans pensée sur les modes d’échec est de l’art performance.

3) La pratique ennuyeuse mais correcte qui a sauvé la situation : corrélation basée sur la tuple

Une entreprise de services financiers avait un environnement mixte : serveurs Ubuntu, un load balancer managé, et un service mesh dans Kubernetes. Des resets apparaissaient comme des 502 intermittents, et les équipes étaient déjà en train de rédiger leurs récits favoris.

Le responsable de garde a fait une chose ennuyeuse : il a standardisé la collecte de preuves d’incident. Pour tout rapport de reset, il fallait fournir : timestamp (UTC), IP client, IP serveur, port de destination, et si possible le port source éphémère. C’est la 5-tuple, moins le protocole parce que c’était toujours TCP.

Avec cela, ils ont fait correspondre une seule requête défaillante entre : journaux client, journaux sidecar du mesh, logs du load balancer, et un tcpdump sur un nœud. La capture a prouvé que le RST provenait du sidecar, pas du conteneur applicatif et pas de l’upstream. Le sidecar appliquait une mise à jour de politique qui avait brièvement invalidé une entrée SAN dans une chaîne de certificats pendant une rotation.

La correction fut de planifier la rotation des certificats avec chevauchement et de valider avec du trafic synthétique avant de basculer la politique. Le gain plus large fut culturel : le format de preuve est devenu réflexe. Pas d’héroïsme. Juste une preuve reproductible.

Erreurs courantes : symptômes → cause racine → correction

Cette section est celle que vous feuilletez pendant qu’un interlocuteur demande « C’est de notre faute ? » en conférence.

1) Les resets arrivent immédiatement à la connexion

  • Symptôme : curl échoue juste après « Connected », ou le SYN reçoit un RST immédiatement.
  • Cause racine : Rien n’écoute sur cette IP:port ; VIP mal routé ; pare-feu qui rejette avec tcp-reset.
  • Correction : Vérifier l’écoute avec ss -ltnp ; vérifier la config VIP ; vérifier nftables pour des règles reject ; confirmer que le service est lié à la bonne adresse.

2) Les resets montent pendant les déploiements

  • Symptôme : rafales d’ECONNRESET pendant le rollout, puis retour à la normale.
  • Cause racine : connexions en vol tuées par redémarrage de processus ; pas de vidage gracieux/drain ; LB envoie encore vers des pods en terminaison.
  • Correction : Ajouter du connection draining ; augmenter terminationGracePeriod ; mettre readiness à false avant SIGTERM ; aligner le délai de désinscription LB avec l’arrêt de l’app.

3) Seules les longues requêtes échouent avec des resets

  • Symptôme : petits endpoints OK ; gros téléchargements/uploads ou appels upstream lents se resetent au milieu du flux.
  • Cause racine : timeout de lecture proxy ; timeouts upstream ; limites de buffer ; appareil L7 qui abort des gros payloads.
  • Correction : Vérifier les timeouts proxy ; ajuster les timeouts upstream ; tuner la taille max du corps ; confirmer si un DLP/WAF injecte des RST.

4) Les resets semblent « aléatoires » sur plusieurs services

  • Symptôme : beaucoup d’apps non liées voient des resets en même temps.
  • Cause racine : dépendance partagée : exhaustion conntrack sur un NAT, LB surchargé, push de politique pare-feu, OOM kernel sur un nœud proxy partagé.
  • Correction : Vérifier compteurs conntrack/max, santé du LB, logs de modification pare-feu, et événements OOM ; stabiliser la couche partagée d’abord.

5) Les resets affectent surtout un type de client (mobile, une région, un ISP)

  • Symptôme : desktop OK, mobile instable ; ou une région échoue.
  • Cause racine : différences MTU/chemin ; comportement NAT opérateur ; appliance de sécurité régionale ; routage Geo vers un POP cassé.
  • Correction : Comparer captures de paquets depuis différents réseaux ; lancer des pings DF ; vérifier les politiques de routage ; valider les configs proxy spécifiques au POP.

6) Resets upstream imputés au « réseau » mais compteurs kernel montrent des drops d’écoute

  • Symptôme : Nginx rapporte un upstream reset ; l’équipe upstream dit « pas nous ».
  • Cause racine : overflow de la file d’accept upstream ou pression backlog SYN ; le processus upstream est trop lent à accepter.
  • Correction : Augmenter le backlog ; tuner net.core.somaxconn, backlog spécifique au service ; corriger la starvation CPU ; ajouter de la capacité ou du rate limiting.

Listes de contrôle / plan pas à pas

Checklist A: « J’ai besoin d’une réponse en 15 minutes »

  1. Reproduire avec curl depuis un hôte contrôlé ; enregistrer l’heure UTC exacte et l’IP/port de destination.
  2. Identifier s’il y a un proxy/LB sur le chemin ; déterminer où TLS se termine.
  3. Capturer les paquets RST sur le proxy/LB (idéal) ou sur le serveur (second choix) pendant 60–120 secondes lors de la reproduction.
  4. Depuis la capture, identifier l’IP émettrice du RST. Si c’est l’IP client, c’est un abort client. Si c’est le VIP du proxy, c’est côté proxy. Si c’est l’IP upstream, c’est upstream.
  5. Corréler avec les logs sur l’émetteur : log d’erreur proxy, log app, journal kernel, logs pare-feu.
  6. Prendre une décision : atténuer (rollback, désactiver une fonctionnalité, contourner le WAF, augmenter les timeouts) pendant l’investigation de la cause racine.

Checklist B: « Prouver rigoureusement pour que la bonne équipe corrige »

  1. Collecter la 5-tuple depuis au moins deux points de vue (proxy + upstream, ou client + proxy).
  2. Capturer dans les deux sens ; ne filtrez pas seulement « tcp-rst » sans contexte. Gardez le flux complet pour une connexion qui échoue.
  3. Confirmer la synchronisation temporelle (NTP) sur tous les nœuds impliqués ; la dérive du clock détruit la corrélation.
  4. Vérifier les compteurs kernel sur l’émetteur présumé (drops d’écoute, aborts, retransmits).
  5. Vérifier l’alignement des timeouts : timeout client, idle timeout proxy, keep-alive upstream, timeouts serveur.
  6. Vérifier la pression sur les ressources (CPU, mémoire, descripteurs) et les redémarrages forcés.
  7. Vérifier les règles pare-feu/nftables pouvant générer des RST et les changements récents.
  8. Documenter la preuve minimale : un extrait pcap + une ligne de log + un changement de compteur qui pointent tous vers le même émetteur.

Checklist C: « Prévenir la prochaine fois »

  1. Standardiser les budgets keep-alive et idle timeout à travers les couches ; ajouter du jitter.
  2. Implémenter un arrêt gracieux et le connection draining pour proxies et apps.
  3. Surveiller l’utilisation conntrack sur les nœuds NAT/proxy.
  4. Exposer les compteurs liés aux resets : TcpExtListenDrops, aborts, nginx 499/502, états de terminaison haproxy.
  5. Exécuter des sondes synthétiques qui suivent séparément les échecs de handshake TLS et les échecs applicatifs.

FAQ

1) « Connection reset by peer » signifie-t-il toujours que le serveur a planté ?

Non. Cela signifie qu’un RST TCP a été reçu. L’émetteur peut être le processus serveur, le kernel du serveur, un proxy, un pare-feu ou même le client lui-même.

2) Comment distinguer reset client vs server dans Nginx ?

Nginx journalise souvent les aborts client comme « client prematurely closed connection » et les aborts upstream comme « recv() failed (104) while reading response header from upstream ». Combinez cela avec une capture de paquets pour être sûr.

3) Pourquoi je vois des resets uniquement pendant la poignée de main TLS ?

Causes courantes : problèmes du terminator TLS, enforcement de certificat/politique par des middleboxes, problèmes MTU/PMTUD, ou un service qui n’écoute pas réellement sur le port attendu derrière un VIP.

4) Un pare-feu peut-il envoyer un reset au lieu de dropper ?

Oui. Beaucoup de jeux de règles utilisent « reject with tcp reset » pour échouer vite. C’est agréable pour l’expérience utilisateur quand c’est intentionnel, et terrible quand c’est accidentel.

5) J’ai capturé et je vois un RST depuis l’IP du proxy. Est-ce que ça prouve que le proxy est fautif ?

Ça prouve que le proxy a envoyé le RST. Le proxy peut réagir à un échec upstream (timeouts, resets upstream) ou appliquer une politique (idle timeout, limites). L’étape suivante : vérifier si le trafic upstream montre une erreur antérieure qui a déclenché la décision du proxy.

6) Quelle est la différence entre un FIN et un RST ?

FIN est une fermeture polie : « j’ai fini d’envoyer ». RST est un abort : « arrête immédiatement ; cette connexion est invalide ». FIN produit généralement un EOF propre dans les applications ; RST produit souvent ECONNRESET.

7) Pourquoi les resets augmentent quand on réduit les timeouts keep-alive ?

Parce que vous augmentez le churn de connexions et la probabilité qu’un côté réutilise une connexion que l’autre côté a déjà abandonnée. Des timeouts inalignés créent des usines à resets.

8) Linux peut-il générer des resets même si l’application va bien ?

Oui. Pas d’écoute, overflow de backlog, règles firewall reject locales, et certaines fermetures de socket abruptes peuvent tous aboutir à des RST envoyés par le kernel.

9) Nous utilisons Kubernetes. Qui est « le peer » maintenant ?

Potentiellement : le VIP Service, kube-proxy/iptables/nft, un Ingress controller, un proxy sidecar, ou le pod lui-même. C’est pourquoi vous capturez au niveau du nœud et au niveau de l’ingress pour trouver le véritable émetteur du RST.

10) Si je ne peux pas lancer tcpdump en production, quelle est la meilleure alternative ?

Logs proxy plus compteurs kernel plus corrélation stricte tuple/temps. C’est une preuve plus faible, mais cela peut toujours prouver la direction. Poussez pour des captures contrôlées et limitées dans le temps comme outil standard d’incident.

Conclusion : suites pratiques qui réduisent vraiment les resets

« Connection reset by peer » n’est pas un diagnostic. C’est un symptôme avec un artefact physique très spécifique : un RST TCP. Si vous pouvez le capturer, vous pouvez arrêter de deviner. Si vous pouvez identifier l’émetteur, vous pouvez arrêter de débattre.

Étapes pratiques :

  1. Adopter le playbook rapide : reproduire, capturer le RST, identifier l’émetteur, corréler les logs.
  2. Standardiser les preuves : timestamp UTC + 5-tuple, à chaque fois. Rendre ça ennuyeux.
  3. Aligner les timeouts de bout en bout, et ne pas « optimiser » les idle timeouts sans tester la réutilisation à longue inactivité.
  4. Renforcer les couches partagées : dimensionnement conntrack, limites mémoire des proxies, arrêt gracieux, revue des règles pare-feu.
  5. Transformer cela en runbook de garde. Votre futur vous mérite moins de surprises.
← Précédent
Email : relais authentifié vs envoi direct — choisissez l’approche qui ne vous fait pas bloquer
Suivant →
GPU d’ordinateurs portables : pourquoi un même nom peut cacher cinq niveaux de performance

Laisser un commentaire