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

Cet article vous a aidé ?

« Connection reset by peer » est l’équivalent réseau d’un haussement d’épaules. La socket était vivante, puis plus. Les logs de votre application rejettent la faute sur le réseau, votre équipe réseau rejette la faute sur l’application, votre proxy clame son innocence, et votre canal d’incident se remplit de ressentis plutôt que de preuves.

Ce texte explique comment transformer ce haussement d’épaules en aveu signé. Sur Ubuntu 24.04, vous pouvez prouver si le reset provient du client, du proxy ou du serveur—jusqu’au paquet, au PID, au timeout et à la couche exacte où la décision a été prise.

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

Au niveau des appels système, « connection reset by peer » se traduit généralement par ECONNRESET. Votre processus a tenté de lire ou d’écrire sur une socket TCP, et le noyau vous a dit : l’autre côté a anéanti la connexion.

En termes TCP, la « mise à mort » est typiquement un paquet avec le drapeau RST. Ce RST peut être envoyé pour plusieurs raisons :

  • Le pair a explicitement réinitialisé la connexion (l’application a fermé brusquement, ou le noyau a décidé que la socket est invalide).
  • Un middlebox a forgé un reset (proxy, pare-feu, load balancer, NAT, IDS). Le « pair » peut être un imposteur bien habillé.
  • La machine a reçu du trafic pour une connexion inconnue (pas de socket, état perdu, port fermé) et a répondu par un RST.

Ce que ce n’est pas :

  • Ce n’est pas un timeout. Les timeouts sont généralement ETIMEDOUT ou des timeouts applicatifs (par ex. le client HTTP abandonne). Un reset est immédiat et explicite.
  • Ce n’est pas une fermeture propre. La fermeture propre est FIN/ACK et résulte en EOF à la lecture, pas en ECONNRESET.
  • Ce n’est pas nécessairement « le serveur a planté ». Parfois c’est le client qui a abandonné, parfois le proxy qui a appliqué une règle, parfois conntrack qui a disparu.

Deux règles empiriques qui tiennent en production :

  1. Si vous n’avez pas capturé les paquets, vous ne savez pas qui a envoyé le RST. Les logs peuvent suggérer. Les paquets prouvent.
  2. S’il y a un proxy, supposez qu’il est impliqué jusqu’à preuve du contraire. Les proxies sont payés pour interférer.

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

Ceci est la version « je suis en astreinte et il est 02:13 ». L’objectif est d’identifier rapidement le goulot (client, proxy ou serveur) pour arrêter l’hémorragie.

Premier : localiser l’origine du reset selon le point de vue

  1. Choisissez un flux défaillant : IP client, IP proxy, IP serveur, port de destination, et fenêtre temporelle (±30 secondes).
  2. Capturez les paquets sur le proxy et le serveur simultanément (ou aussi près que possible). Si vous ne pouvez en faire qu’un, capturez sur le proxy.
  3. Cherchez le premier RST dans chaque capture et identifiez quelle interface l’a vu en premier et depuis quelle combinaison IP/port.

Deuxième : corréler avec le processus et les logs

  1. Sur l’hôte émetteur, mappez le 4‑tuple à une socket et au processus propriétaire avec ss -tnpi.
  2. Vérifiez les logs du proxy pour des resets upstream vs aborts côté client vs timeouts (ce sont des formulations différentes ; ne les mélangez pas).
  3. Vérifiez les logs du serveur pour des erreurs d’accept, des alertes TLS, des exceptions applicatives, des redémarrages de workers.

Troisième : valider les causes systémiques

  1. Épuisement de conntrack (chemin NAT ou pare-feu) et épuisement des ports éphémères (côté client) peuvent créer des resets « aléatoires ».
  2. Problèmes MTU/PMTUD peuvent ressembler à des resets quand des appareils se comportent mal, surtout avec des tunnels/VPN.
  3. Contre-pression et timeouts : un proxy avec des timeouts agressifs réinitialisera des upstreams « lents ». Lent peut signifier CPU, stockage, GC ou contention de verrous.

Blague n°1 : Le moyen le plus rapide de trouver le coupable est d’ajouter un proxy devant lui—maintenant vous avez deux coupables et une invitation à une réunion.

Un modèle de preuve : comment arrêter de deviner et commencer à attribuer la faute

Quand vous essayez de prouver d’où vient un RST, vous avez besoin d’une méthode qui survive la politique, pas seulement la physique. Voici le modèle que j’utilise en postmortems :

1) Définir le flux et ses « ancres de vérité »

Une requête peut traverser : client → NAT → proxy d’entrée → proxy de service → backend. Les seules choses que vous pouvez traiter comme « ancres de vérité » sont :

  • Captures de paquets à plusieurs points.
  • État des sockets au niveau noyau (ss, /proc), idéalement sur l’hôte qui a envoyé le RST.
  • Logs horodatés avec des IDs de corrélation qui se propagent réellement.

2) Utiliser une timeline, pas des ressentis

Vous cherchez une séquence comme :

  • SYN/SYN-ACK/ACK établit la connexion.
  • Les données circulent (ou pas).
  • Une partie envoie un RST (ou un appareil l’injecte).

Le « qui » est le saut qui a émis le premier RST, pas l’hôte qui a logué la première exception.

3) Prouver la directionnalité avec le 4‑tuple et les numéros ACK

Les paquets RST ne sont pas que des drapeaux. Ils incluent des numéros de séquence/acknowledgment. Lorsque vous capturez des deux côtés, vous pouvez souvent déterminer si un reset est :

  • Généré localement par l’hôte que vous sniffiez (il quitte l’interface avec le routage correct, MAC attendu, motifs TTL attendus).
  • Transité (vu entrant sur un proxy depuis l’amont, puis vu séparément sortant vers le client, parfois avec un remappage de ports).
  • Injecté (TTL étrange, MAC/vendor OUI inattendu, ou apparaissant uniquement d’un côté).

4) Décider ce que « pair » signifie dans votre architecture

Si le client parle à un proxy, le « pair » du point de vue du client est le proxy. Le serveur backend peut être innocent tandis que le proxy réinitialise. Ce n’est pas de la sémantique ; ça change l’équipe qui corrige.

5) Traiter les timeouts comme des décisions de politique

Beaucoup de resets ne sont pas des « échecs » ; ce sont des mesures d’application. Exemple : le proxy voit l’amont inactif trop longtemps, envoie un RST au client pour libérer des ressources. C’est une décision produit déguisée en réseau.

Faits intéressants et contexte historique (la version utile)

  • Fait 1 : Le comportement du RST TCP a été formalisé tôt ; les resets existent pour tuer rapidement les connexions invalides au lieu d’attendre des timeouts. C’est pourquoi les resets sont si brutaux.
  • Fait 2 : Beaucoup d’incidents classiques « connection reset » dans les années 2000 étaient en fait des timeouts NAT sur des firewalls à état. Le NAT vous oublie ; vos paquets deviennent « venus de nulle part ».
  • Fait 3 : Linux peut envoyer un RST quand un paquet arrive pour un port sans socket à l’écoute—fréquent lors de déploiements où le processus redémarre et perd l’état d’accept.
  • Fait 4 : Les proxies ont popularisé la distinction « client abort » vs « upstream reset » ; les logs modernes de Nginx/HAProxy/Envoy peuvent généralement indiquer quelle direction a cassé en premier.
  • Fait 5 : L’« injection de RST » a été utilisée historiquement pour la censure et l’interférence. En entreprise, le cousin moins dramatique est « l’appliance de sécurité qui termine les flux ».
  • Fait 6 : HTTP/2 et HTTP/3 ont déplacé certains modes de défaillance : un reset d’une connexion HTTP/2 élimine plusieurs streams à la fois, amplifiant l’impact.
  • Fait 7 : Ubuntu 24.04 est livré avec un noyau et un userland plus récents ; des changements de paramètres par défaut (par ex. contrôle de congestion, bibliothèques TLS, services systemd) peuvent déplacer le point où un reset apparaît, même si votre appli n’a pas changé.
  • Fait 8 : Les keepalives ont été inventés pour éviter que des connexions semi-ouvertes traînent indéfiniment, mais keepalive agressif + timeout NAT court peut encore créer le genre de reset « ça marchait hier ».

Tâches pratiques : commandes, sorties et décisions (12+)

Ces tâches sont écrites pour être exécutées sur des hôtes Ubuntu 24.04. Chaque tâche inclut : la commande, ce que signifie la sortie, et quelle décision prendre.

Task 1: Confirm the error is actually ECONNRESET and where it appears

cr0x@server:~$ journalctl -u myapp --since "10 min ago" | tail -n 30
Dec 31 01:58:12 api-1 myapp[21450]: error: read ECONNRESET from 10.20.4.18:443
Dec 31 01:58:12 api-1 myapp[21450]: request_id=9b2a... upstream=payments duration_ms=212

Ce que cela signifie : L’application a vu un reset lors d’une lecture. Cela suggère que le pair (du point de vue de cet hôte) a envoyé un RST ou qu’un dispositif intermédiaire l’a fait.

Décision : Identifiez ce que « pair » signifie à ce saut. Si 10.20.4.18:443 est un VIP de proxy, commencez par le proxy.

Task 2: Resolve the network path: who is the peer, really?

cr0x@server:~$ ip route get 10.20.4.18
10.20.4.18 via 10.20.1.1 dev ens5 src 10.20.2.17 uid 0
    cache

Ce que cela signifie : Le trafic vers le pair sort via ens5 et la gateway 10.20.1.1. Pas de surprise d’overlay ici.

Décision : Capturez sur ens5. Si la route passe par un device tunnel, suspectez aussi MTU et PMTUD.

Task 3: Snapshot live TCP health (server or proxy)

cr0x@server:~$ ss -s
Total: 1218
TCP:   842 (estab 311, closed 377, orphaned 2, timewait 355)

Transport Total     IP        IPv6
RAW       0         0         0
UDP       14        12        2
TCP       465       419       46
INET      479       431       48
FRAG      0         0         0

Ce que cela signifie : Beaucoup de TIME_WAIT est normal sur un trafic HTTP chargé ; « orphaned » bas est bon. Un grand nombre d’« orphaned » peut indiquer des fermetures abruptes et une pression sur le noyau.

Décision : Si TIME_WAIT explose côté client et que vous êtes proche des limites de ports éphémères, envisagez la réutilisation de connexions et vérifiez la plage de ports (tâches ultérieures).

Task 4: Find the exact socket and owning process for a failing connection

cr0x@server:~$ ss -tnpi dst 10.20.4.18:443 | head
ESTAB 0 0 10.20.2.17:51644 10.20.4.18:443 users:(("myapp",pid=21450,fd=78)) timer:(keepalive,116min,0)

Ce que cela signifie : La connexion existe et appartient à myapp. Le timer keepalive est réglé ; l’appli réutilise des connexions.

Décision : Si des resets surviennent sur des connexions réutilisées, suspectez des timeouts inactifs sur proxies/NAT. Vérifiez les timeouts du proxy et du NAT.

Task 5: Capture packets on the host where you suspect the RST originates

cr0x@server:~$ sudo tcpdump -i ens5 -nn -s 0 -vvv 'host 10.20.4.18 and tcp port 443' -c 30
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
01:58:12.041223 IP 10.20.2.17.51644 > 10.20.4.18.443: Flags [P.], seq 182:496, ack 911, win 501, length 314
01:58:12.042007 IP 10.20.4.18.443 > 10.20.2.17.51644: Flags [R.], seq 911, ack 496, win 0, length 0
30 packets captured

Ce que cela signifie : Le pair (10.20.4.18) a envoyé un RST immédiatement après avoir reçu des données. Ce n’est pas un timeout ; c’est un refus actif en plein flux.

Décision : Concentrez-vous sur les logs et le processus du pair. Si 10.20.4.18 est un VIP de proxy, capturez sur l’hôte proxy pour savoir s’il a reçu un RST upstream ou s’il en a généré un.

Task 6: Capture on the proxy: prove whether it’s upstream or client-side

cr0x@server:~$ sudo tcpdump -i ens5 -nn -s 0 -vvv '(host 10.20.2.17 and tcp port 443) or (host 10.30.7.55 and tcp port 8443)' -c 50
tcpdump: listening on ens5, link-type EN10MB (Ethernet), snapshot length 262144 bytes
01:58:12.041980 IP 10.20.2.17.51644 > 10.20.4.18.443: Flags [P.], seq 182:496, ack 911, length 314
01:58:12.042001 IP 10.20.4.18.443 > 10.20.2.17.51644: Flags [R.], seq 911, ack 496, length 0
01:58:12.041700 IP 10.20.4.18.41210 > 10.30.7.55.8443: Flags [P.], seq 991:1305, ack 4402, length 314
01:58:12.041750 IP 10.30.7.55.8443 > 10.20.4.18.41210: Flags [P.], seq 4402:4520, ack 1305, length 118

Ce que cela signifie : Le proxy a reset la connexion client alors que l’amont répondait encore normalement. C’est un RST généré par le proxy.

Décision : Arrêtez d’accuser le backend. Auditez les timeouts du proxy, les limites de taille de requête, les paramètres TLS et la santé des workers. Le proxy prend la décision.

Task 7: Check Nginx logs for client abort vs upstream reset (if Nginx)

cr0x@server:~$ sudo tail -n 20 /var/log/nginx/error.log
2025/12/31 01:58:12 [info] 1192#1192: *884 client prematurely closed connection while reading client request body, client: 10.20.2.17, server: api.example, request: "POST /v1/payments HTTP/1.1"
2025/12/31 01:58:12 [error] 1192#1192: *885 upstream prematurely closed connection while reading response header from upstream, client: 10.20.2.17, server: api.example, request: "GET /v1/status HTTP/1.1", upstream: "http://10.30.7.55:8443/v1/status"

Ce que cela signifie : Deux problèmes différents. « Client prematurely closed » pointe vers un abort client. « Upstream prematurely closed » pointe vers le backend qui ferme prématurément (crash, mismatch de keepalive, fermeture applicative, ou timeout upstream).

Décision : Séparez les incidents selon la formulation des logs. Traitez-les comme des arbres de cause racine différents, pas comme un seul « glitch réseau ».

Task 8: Check HAProxy termination state (if HAProxy)

cr0x@server:~$ sudo tail -n 5 /var/log/haproxy.log
Dec 31 01:58:12 lb-1 haproxy[2034]: 10.20.2.17:51644 [31/Dec/2025:01:58:12.041] fe_https be_api/api-3 0/0/1/2/3 200 512 - - ---- 12/12/0/0/0 0/0 "GET /v1/status HTTP/1.1"
Dec 31 01:58:12 lb-1 haproxy[2034]: 10.20.2.17:51645 [31/Dec/2025:01:58:12.050] fe_https be_api/api-2 0/0/0/0/1 0 0 - - cD-- 3/3/0/0/0 0/0 "POST /v1/payments HTTP/1.1"

Ce que cela signifie : Les flags de terminaison comme cD-- suggèrent fortement un abort côté client (« client » + « data »). La requête n’a pas été complétée.

Décision : Si HAProxy rapporte des aborts clients mais que les clients jurent ne pas avoir coupé, vérifiez les timeouts clients et les intermédiaires (réseaux mobiles, proxies d’entreprise, paramètres par défaut des SDK).

Task 9: Check Envoy for upstream reset reasons (if Envoy)

cr0x@server:~$ sudo journalctl -u envoy --since "10 min ago" | tail -n 10
Dec 31 01:58:12 edge-1 envoy[1640]: [debug] upstream reset: reset reason: connection termination, transport failure reason: delayed connect error: 111
Dec 31 01:58:12 edge-1 envoy[1640]: [debug] downstream connection termination

Ce que cela signifie : Envoy donne une raison de reset upstream et souvent une raison transport. « connect error: 111 » est une connexion refusée (RST depuis l’amont parce qu’il n’y a pas d’écoute).

Décision : Traitez « connection refused » comme un problème de disponibilité du backend (service non à l’écoute, mauvais port, déploiement raté), pas comme des resets aléatoires.

Task 10: Check kernel counters for listen/backlog pain

cr0x@server:~$ netstat -s | egrep -i 'listen|overflow|reset' | head -n 20
    14 times the listen queue of a socket overflowed
    14 SYNs to LISTEN sockets ignored
    2318 connection resets received
    1875 connections reset sent

Ce que cela signifie : Un overflow de queue d’écoute peut faire en sorte que les clients voient des resets ou des échecs de connexion sous des rafales. « reset sent » indique que cet hôte envoie activement des resets aux pairs.

Décision : Si l’overflow de la file d’attente d’écoute augmente pendant les incidents, réglez backlog (somaxconn, backlog d’accept de l’app), ou scalez. Ne touchez pas d’abord aux timeouts ; vous ne ferez que masquer le symptôme.

Task 11: Check conntrack exhaustion (common on NAT/proxy boxes)

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

Ce que cela signifie : Vous êtes quasiment plein. Une fois que conntrack est saturé, de nouveaux flux sont dropés ou corrompus. Les erreurs applicatives résultantes peuvent inclure resets, timeouts et handshakes partiels bizarres.

Décision : Augmentez nf_conntrack_max si la mémoire le permet, réduisez les timeouts pour les flux morts, et réduisez le churn de connexions (keepalive, pooling). Trouvez également la source de l’explosion de connexions.

Task 12: Validate client ephemeral port range and TIME_WAIT pressure

cr0x@server:~$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768	60999

Ce que cela signifie : Environ 28k ports disponibles par IP source. Avec beaucoup de connexions de courte durée, vous pouvez manquer de ports et obtenir des échecs de connexion qui se manifestent en resets en amont.

Décision : Préférez keepalive/pooling ; si nécessaire, élargissez la plage de ports et envisagez plus d’IPs sources (pools SNAT) sur des clients haut débit.

Task 13: Check for MTU blackholes and PMTUD breakage

cr0x@server:~$ ping -M do -s 1472 10.20.4.18 -c 3
PING 10.20.4.18 (10.20.4.18) 1472(1500) bytes of data.
From 10.20.2.17 icmp_seq=1 Frag needed and DF set (mtu = 1450)
From 10.20.2.17 icmp_seq=2 Frag needed and DF set (mtu = 1450)
From 10.20.2.17 icmp_seq=3 Frag needed and DF set (mtu = 1450)

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

Ce que cela signifie : Le MTU du chemin est 1450, pas 1500. Si quelque chose bloque l’ICMP « frag needed », certaines sessions TCP se bloquent puis sont tuées par des intermédiaires, parfois via des resets.

Décision : Corrigez la cohérence MTU (tunnels, VXLAN), assurez-vous que l’ICMP est autorisé pour PMTUD, ou clamppez MSS aux bords.

Task 14: Confirm whether TLS handshake is being reset

cr0x@server:~$ openssl s_client -connect 10.20.4.18:443 -servername api.example -tls1_2 
CONNECTED(00000003)
write:errno=104

Ce que cela signifie : Errno 104 est ECONNRESET. Un reset pendant le handshake pointe souvent vers la politique TLS du proxy (SNI requis, suites, ALPN), du rate limiting, ou un backend qui ne parle pas TLS sur ce port.

Décision : Comparez avec un client connu bon, vérifiez SNI, inspectez la config TLS du proxy, et capturez les paquets pour voir qui envoie le RST.

Task 15: Inspect live drops and errors at the interface

cr0x@server:~$ ip -s 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 06:3a:9e:11:22:33 brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast
    1287349832 1298374      0     412       0       0
    TX:  bytes packets errors dropped carrier collsns
    983748233  992331      0       9       0       0

Ce que cela signifie : Il y a des drops RX non nuls. Les drops peuvent provoquer des retransmissions et déclencher des politiques de timeout qui se terminent en resets.

Décision : Si les drops se corrèlent aux périodes d’incident, examinez le queueing NIC, la saturation CPU de l’hôte et la congestion en amont. Ne chassez pas des fantômes applicatifs tant que le chemin paquet n’est pas stable.

Task 16: Tie a reset to a local firewall rule (nftables/ufw)

cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
  chain input {
    type filter hook input priority 0; policy accept;
    tcp dport 443 ct state new limit rate 50/second accept
    tcp dport 443 ct state new reject with tcp reset
  }
}

Ce que cela signifie : Vous envoyez explicitement des resets TCP lorsque le rate limit est dépassé. Ce n’est pas « le réseau ». C’est une politique.

Décision : Si les resets augmentent pendant les rafales, ajustez les rate limits, ajoutez du buffering/queueing, ou scalez. Aussi : documentez la règle pour que le prochain astreint ne la redécouvre pas à 3h du matin.

Modes de défaillance spécifiques aux proxies (Nginx, HAProxy, Envoy)

Si vous exécutez n’importe quel type de proxy, vous êtes dans le business du « connection reset by peer », que ça vous plaise ou non. Voici les motifs qui comptent.

Le proxy réinitialise le client parce que l’amont est lent

Commun quand :

  • Les en-têtes de réponse upstream prennent trop de temps (proxy_read_timeout dans Nginx ; timeouts dans HAProxy/Envoy).
  • Les buffers du proxy sont pleins ; il choisit de tuer des connexions plutôt que de sombrer avec le navire.

Comment le prouver : Une capture sur le proxy montre un RST côté client sans RST correspondant côté upstream au même moment. Les logs du proxy montrent timeout/504/termination state.

Correction : Décidez d’augmenter les timeouts ou de corriger la latence upstream. Augmenter les timeouts est un pari que la lenteur est « normale ». En production, la lenteur est rarement normale ; c’est un symptôme.

Le proxy réinitialise à cause de limites de taille de corps ou d’en-têtes

Les proxies peuvent fermer les connexions agressivement sur des en-têtes/corps trop grands. Selon la config et le timing, le client peut voir un reset plutôt qu’un propre 413/431.

Comment le prouver : Les logs d’erreur du proxy mentionnent « client sent too large request » ou des échecs de parsing d’en-têtes. La capture montre un RST peu après que le client ait envoyé les en-têtes/corps.

Correction : Augmentez les limites avec précaution et intention. Si vous les augmentez globalement, vous augmentez aussi le rayon d’impact pour l’abus et la pression mémoire.

Mismatch de keepalive entre proxy et upstream

Un des pièges de production les plus courants : le proxy recycle des connexions upstream plus longtemps que ce que l’amont accepte. L’amont ferme les connexions inactives. Le proxy tente d’écrire sur une socket morte. Le reset arrive quelque part, et votre message d’erreur pointe le mauvais saut.

Comment le prouver : Sur le proxy : la connexion upstream dans ss affiche des timers keepalive ; les logs upstream montrent des fermetures pour idle timeout ; la capture montre un FIN/RST upstream sur une connexion idle suivi par une écriture du proxy.

Correction : Alignez les timeouts keepalive : SDK client, proxy edge, proxy de service, serveur backend, et NAT. Choisissez une hiérarchie (edge le plus petit ou backend le plus petit) et documentez-la.

Application de politique TLS (SNI/ALPN/ciphers) qui ressemble à des resets aléatoires

Les modes d’échec TLS sont souvent mal loggés par les applications et parfois gérés par des proxies avec des fermetures dures. Un client sans SNI peut se prendre un reset. Un client ancien offrant des suites faibles peut se prendre un reset. La négociation HTTP/2 complique ça quand ALPN est impliqué.

Comment le prouver : openssl s_client montre un reset pendant le handshake ; les logs du proxy montrent des erreurs de handshake ; la capture montre un reset juste après le ClientHello.

Correction : Appliquez la politique TLS, mais rendez-la observable. Si vous allez resetter, logguez pourquoi avec assez de détails pour être actionnable.

Causes côté serveur : application, noyau, TLS, stockage (oui, le stockage)

Les resets sont parfois la faute du serveur de manière ennuyeuse : le serveur est surchargé, mal configuré ou redémarre, et le noyau fait ce que font les noyaux—empirer la journée de votre appli.

Redémarrages d’application et churn de connexions

Si votre backend redémarre sous charge (OOM, crash loop, déploiement), les connexions existantes peuvent être abandonnées. Les clients voient des resets si le processus meurt ou si un sidecar/proxy démonte des sockets de façon abrupte.

Preuve : Corrélez les timestamps de reset avec les redémarrages de service dans journalctl et les événements de l’orchestrateur. La capture montre des RST coïncidant avec des SYN arrivant sur aucun listener.

Overflow de backlog d’écoute

Un serveur peut être « up » mais ne pas accepter de connexions. Si la queue d’accept déborde, les SYNs sont dropés ou ignorés ; les clients retentent ; les intermédiaires réagissent ; finalement quelqu’un reset.

Preuve : netstat -s montre des overflows d’écoute ; le CPU du serveur peut être saturé ; les logs applicatifs montrent des accepts lents.

Correction : Tondez backlog, scalez, et réduisez le coût par connexion. Ne le masquez pas avec des timeouts plus grands.

Latence de stockage qui se déguise en resets réseau

C’est là que les ingénieurs stockage se font traîner dans des « problèmes réseau ». Si vos threads serveur bloquent sur le disque (fsync DB, saturation du volume de logs, hiccups EBS, écritures sync ZFS), la latence des requêtes explose. Les proxies atteignent leurs timeouts et resetent les clients. Les clients accusent le serveur. Le serveur accuse le réseau. Personne n’accuse le disque parce qu’il est « vert ».

Preuve : Pics de latence dans les métriques applicatives corrélés aux timeouts du proxy et aux resets clients. Sur le serveur, iostat et pidstat montrent de l’attente IO pendant la fenêtre d’incident.

Correction : Traitez la latence stockage comme partie du chemin de requête. Mettez des SLOs dessus. Si vous ne pouvez pas la mesurer, vous ne pouvez pas l’innocenter.

Confusion sur l’offload TLS

Des backends qui parlent accidentellement HTTP sur un port où le proxy attend TLS (ou l’inverse) peuvent produire des resets qui ressemblent à des flaky handshakes. C’est un classique de dérive de config.

Preuve : La capture montre du clair là où TLS devrait être, ou un reset immédiat après ClientHello. Les logs du proxy montrent « wrong version number » ou échec de handshake.

Correction : Rendre explicites les contrats port/protocole. Traitez « c’était toujours comme ça » comme un bug, pas comme une raison.

Causes côté client : aborts, NATs, MTU et bibliothèques « aidantes »

Les clients causent un nombre surprenant de resets. Pas par malveillance—par impatience, timeouts par défaut, et middleboxes qui garbage-collectent agressivement l’état.

Aborts client dus à un timeout local

Les SDKs par défaut ont souvent des timeouts courts. Les clients mobiles sont pires parce que les réseaux sont pires. Les clients d’entreprise peuvent être les pires à cause des proxies de sécurité créatifs.

Preuve : Les logs du proxy disent « client prematurely closed connection » ou l’état de terminaison HAProxy indique abort client. La capture sur le proxy montre FIN/RST venant du client en premier.

Correction : Rendre explicites les timeouts clients et les aligner avec le comportement serveur/proxy. Si vous avez besoin de 30 secondes pour une requête, ne livrez pas un timeout client à 5 secondes et accusez ensuite le serveur.

Timeouts NAT tuant des connexions longue-durée inactives

Un client derrière un NAT peut garder une connexion TCP inactive plus longtemps que le timeout NAT. Le NAT oublie la mapping. Le prochain paquet sortant ne trouve pas d’état en retour, et un appareil peut répondre par un RST ou dropper.

Preuve : Les resets surviennent après des périodes d’inactivité ; changer l’intervalle de keepalive change le taux d’échec. Les tables conntrack du NAT montrent des timeouts courts pour les flows établis.

Correction : Keepalives à des intervalles plus courts que le timeout NAT, ou évitez les connexions longue-durée inactives dans des réseaux hostiles.

Mismatch MTU et blackholes

Les problèmes MTU ne devraient pas causer directement des resets. Dans un monde idéal, ils provoquent de la fragmentation ou un ajustement PMTUD. Dans le monde réel, certains appareils bloquent l’ICMP, et d’autres prennent des décisions « aidantes » comme tuer des flux.

Preuve : ping -M do montre un MTU de chemin plus petit que prévu ; la capture montre des retransmissions puis des resets par le proxy après des timeouts.

Correction : Corrigez le MTU du chemin ou clamppez MSS au bord des tunnels.

Blague n°2 : « C’est probablement le client » n’est pas un diagnostic ; c’est un mécanisme d’adaptation avec un numéro de ticket.

Trois mini-récits du monde corporate

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Ils avaient une narration propre : « La base de données réinitialise les connexions. » L’erreur était ECONNRESET, la stack trace pointait vers le driver DB, et le service time-outait sous charge. L’équipe DB a été réveillée, encore, et tout le monde se préparait pour la danse habituelle.

Un ingénieur senior posa la question impolie mais nécessaire : « D’où vient le reset ? » Personne ne le savait. Ils avaient métriques, dashboards et une war room ; ils n’avaient pas de capture de paquets.

Ils ont capturé à deux points : l’hôte applicatif et la couche HAProxy. Sur l’hôte applicatif, le RST semblait venir du VIP HAProxy. Sur HAProxy, il n’y avait pas de RST upstream de la base. Au lieu de cela, HAProxy émettait des resets parce que son timeout côté client était plus court que le plus lent percentile des requêtes DB pendant un peak d’attente IO.

La mauvaise hypothèse était que le « pair » dans le message d’erreur était la base de données. En réalité le pair était le proxy. La base était lente, oui, mais elle ne resetait rien ; le proxy appliquait une politique.

La correction n’a pas été « augmenter les timeouts pour toujours ». Ils ont légèrement augmenté le timeout proxy, mais le vrai travail a été de réduire les stalls IO—planification de maintenance d’index, meilleurs plans de requête, et déplacer une charge de logging hors du même volume. Après ça, le proxy a arrêté d’« aider ».

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une équipe a essayé de réduire la latence et le CPU en augmentant la réutilisation keepalive partout. Moins de handshakes, moins de setups TCP, moins d’overhead TLS. Sur le papier, propre.

Ils l’ont déployé d’abord au proxy edge, laissant les connexions upstream vivre beaucoup plus longtemps. En moins d’un jour, des clients ont rapporté des resets sporadiques sur des requêtes parfaitement ordinaires. Les logs étaient énervants : certaines requêtes réussissaient instantanément, d’autres mouraient en plein requête avec « connection reset by peer ».

La cause racine était un mismatch : le serveur application upstream avait un timeout idle agressif et recyclait parfois des workers. Le proxy, maintenant tenant des connexions upstream plus longtemps, réutilisait des sockets qui avaient été silencieusement tuées. La première écriture sur une connexion mi-morte déclenchait un reset. Sous haute concurrence, ça paraissait aléatoire.

Les captures de paquets l’ont rendu évident : l’amont envoyait FIN sur l’idle ; le proxy ne le remarquait pas à temps ; le proxy écrivait ; le noyau répondait avec un comportement RST. L’« optimisation » a augmenté la probabilité de toucher des sockets upstream obsolètes.

La correction a été ennuyeuse : aligner les timeouts, activer des health checks actifs qui valident réellement des requêtes, et régler le keepalive upstream à quelque chose de moins héroïque. Les performances se sont améliorées après qu’ils aient arrêté d’être trop malins.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe plateforme avait une règle : tout incident client-facing impliquant des erreurs réseau nécessite une « capture à deux côtés ». Les gens se plaignaient parce que c’était lent et procédural. C’était lent, et c’était le but.

Un après-midi, un service interne commença à échouer avec des resets. L’équipe applicative insistait que c’était le proxy. L’équipe proxy insistait que c’était l’appli. L’équipe réseau insistait que c’était « upstream ». Le canal d’incident commençait à sentir la réunion interminable.

Ils ont suivi la règle. Capture sur l’interface client-facing du proxy et sur son interface upstream. Le reset était visible venant d’un hop pare-feu entre le proxy et le backend, uniquement sur le côté upstream. Le proxy ne faisait que relayer la douleur.

Parce qu’ils avaient des preuves tôt, ils ont évité une heure de bidouillage de config. La fenêtre de changement du pare-feu montrait un ajustement récent qui rejetait certains nouveaux flux avec TCP reset après un seuil de rate. Ce n’était pas malveillant ; c’était « protecteur ».

La correction a été d’ajuster la règle de pare-feu pour correspondre au profil de rafale légitime du service et d’ajouter une exception explicite pour le sous-réseau du proxy. Le taux de reset a chuté immédiatement. Personne n’a eu à prétendre qu’il « sentait » le problème.

Erreurs courantes : symptôme → cause racine → fix

  • Symptôme : Resets uniquement sur de gros POSTs.
    Cause racine : Limite de taille de corps du proxy ou comportement de buffering ; parfois WAF terminant les flux.
    Fix : Augmenter les limites ciblées (client_max_body_size), ajuster le buffering, confirmer la politique WAF ; vérifier avec capture paquets et logs proxy.
  • Symptôme : Resets après ~60 secondes d’inactivité sur connexions réutilisées.
    Cause racine : Timeout idle NAT/proxy plus court que la période de réutilisation keepalive du client.
    Fix : Aligner keepalive/idle timeouts ; envisager le tuning TCP keepalive ; réduire la réutilisation idle.
  • Symptôme : Le trafic en rafale cause des resets/connection refused immédiats.
    Cause racine : Overflow de backlog d’écoute ou rate limiting qui rejette avec TCP reset.
    Fix : Tuner le backlog, scaler, ou ajuster le rate limiting ; confirmer avec netstat -s et règles firewall.
  • Symptôme : Seulement certains clients (SDKs anciens) voient des resets pendant le handshake TLS.
    Cause racine : Application de politique TLS : SNI manquant, suites non supportées, mismatch ALPN ; proxy ferme brutalement.
    Fix : Rendre explicites les exigences TLS ; améliorer le reporting d’erreur ; tester avec openssl s_client en émulation du client.
  • Symptôme : Resets corrélés aux déploiements mais seulement un pourcentage du trafic échoue.
    Cause racine : Draining de connexions non configuré ; le proxy envoie du trafic à des instances redémarrant ; sockets upstream keepalive obsolètes.
    Fix : Ajouter arrêt gracieux, readiness gates, draining, et keepalive upstream plus court.
  • Symptôme : Resets « aléatoires » sous fort churn de connexions ; hôtes NAT/proxy montrent des comportements étranges.
    Cause racine : Table conntrack presque pleine ; drops/évictions créent une perte d’état.
    Fix : Augmenter conntrack max, réduire le churn, tuner les timeouts, identifier les top talkers.
  • Symptôme : Resets groupés avec drops de paquets et retransmissions.
    Cause racine : Drops d’interface, overruns de queue, saturation CPU, ou congestion du chemin menant à des timeouts politiques et resets.
    Fix : Corriger la perte de paquets d’abord ; ensuite tuner les timeouts. Utiliser ip -s link et des captures pour confirmer.
  • Symptôme : Resets uniquement à travers des VPN/tunnels.
    Cause racine : Mismatch MTU ou ICMP bloqué causant l’échec PMTUD ; les proxies terminent les flux lents/stallés.
    Fix : Clamp MSS, aligner MTU, autoriser ICMP « frag needed », valider avec DF pings.

Listes de contrôle / plan étape par étape

Plan étape par étape : prouver client vs proxy vs serveur en moins d’une heure

  1. Choisissez une requête défaillante : timestamp, IP client, VIP de destination, chemin de requête, ID de corrélation si disponible.
  2. Identifiez le premier saut proxy (ingress/load balancer). Dans les stacks modernes, c’est souvent le « pair » réel pour le client.
  3. Capturez les paquets à deux points :
    • Sur le proxy : interface côté client.
    • Sur le proxy : interface côté upstream (ou sur l’hôte backend).
  4. Trouvez le premier RST et notez :
    • IP:port source et IP:port destination
    • L’heure exacte
    • Si c’est [R.] ou [R] et si c’est en réponse à des données
  5. Corrélez avec les logs sur l’hôte qui a envoyé le RST :
    • Formulations d’erreur Nginx
    • Flags de terminaison HAProxy
    • Raisons de reset upstream Envoy
    • Compteurs noyau (netstat -s)
  6. Vérifiez les contraintes systémiques (rapidement) :
    • compte conntrack vs max
    • dropped interface
    • saturation CPU / IO wait
  7. Prenez une décision :
    • Si le proxy a généré le RST : corriger la config/timeouts/policy du proxy.
    • Si le backend a généré le RST : corriger le cycle de vie de l’app, le listener, TLS, ou la surcharge.
    • Si le client a généré le RST : corriger la logique de timeout/retry du client ou le chemin réseau client.
    • Si un middlebox l’a injecté : corriger la politique du firewall/WAF/NAT ou les tables d’état.

Checklist opérationnelle : quoi enregistrer pour un rapport d’incident défendable

  • Extrait pcap(s) montrant le RST à deux points de vue
  • Le(s) 4‑tuple(s) impliqué(s) et tout mapping NAT si connu
  • Ligne(s) de logs du proxy pour la même requête
  • Preuves de redémarrage ou de surcharge du serveur (journald, métriques)
  • État conntrack et plage de ports si pertinent
  • Les réglages de config exacts impliqués (timeouts, limites, rate limits)

FAQ

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

Non. Cela signifie que quelqu’un à l’autre extrémité de ce saut TCP a envoyé un reset—ou qu’un middlebox s’est fait passer pour lui. Dans des architectures proxifiées, le « pair » est souvent le proxy.

2) Comment prouver que le proxy a envoyé le reset ?

Capturez sur le proxy. Si le proxy envoie RST au client alors que le côté upstream ne montre pas de RST correspondant (et peut même envoyer des données), le proxy est l’origine.

3) Un pare-feu peut-il causer des resets au lieu de drops ?

Oui. Les firewalls et WAFs « reject with tcp reset » pour échouer rapidement. C’est courant avec des rate limits, état invalide ou violations de politique.

4) Pourquoi ne le vois-je que sur des connexions keepalive ?

Parce que des connexions obsolètes sont réutilisées. Les timeouts idle NAT, proxy et backend ne s’alignent pas automatiquement. La première écriture après une période d’inactivité révèle que vous parliez à un fantôme.

5) La capture de paquets est-elle vraiment nécessaire ? Puis-je m’en sortir avec des logs ?

Les logs peuvent suggérer ; les paquets peuvent prouver. Quand plusieurs équipes sont impliquées, « suggérer » achète des réunions. La preuve achète une correction.

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

FIN est une fermeture polie : la lecture renvoie EOF. RST est un abort brutal : lecture/écriture échoue avec ECONNRESET. Beaucoup de proxies et moteurs de politique choisissent RST pour récupérer rapidement des ressources.

7) Les problèmes de stockage peuvent-ils vraiment conduire à des resets de connexion ?

Indirectement, oui. La latence stockage peut rendre les backends lents, déclenchant des timeouts proxy ; le proxy reset alors le client. Le reset est un symptôme ; la cause racine peut être l’attente IO.

8) Comment distinguer un abort client d’un abort serveur ?

Les logs du proxy sont généralement l’indice le plus rapide (client prematurely closed vs upstream closed). Pour la preuve, la capture de paquets montre quel côté envoie FIN/RST en premier.

9) Et si je vois des resets mais que personne ne logge rien ?

Alors il vous manque de l’observabilité à la couche qui a pris la décision. Ajoutez du logging structuré sur les proxies (raison de terminaison), conservez des pcaps courts pendant les incidents, et enregistrez l’état des sockets.

Conclusion : prochaines étapes pratiques

Vous ne corrigez pas « connection reset by peer ». Vous corrigez le composant qui a décidé d’envoyer un reset—ou la condition qui l’y a forcé.

Prochaines étapes qui rapportent :

  1. Adopter la règle de capture à deux côtés pour les incidents réseau. Ça met fin aux arguments rapidement.
  2. Normaliser les budgets de timeout entre clients, proxies et serveurs. Écrivez-les. Faites-les partie des revues de changement.
  3. Rendre les resets observables : raisons de terminaison proxy, compteurs de reject firewall, alertes de saturation conntrack, alertes d’overflow de backlog.
  4. Exécuter les tâches ci‑dessus pendant l’incident et coller les sorties dans le ticket. Le vous du futur remerciera le vous du passé.

Idée paraphrasée (attribuée) : Werner Vogels a souligné que vous devriez « construire des systèmes qui supposent l’échec et se rétablissent rapidement », parce que l’échec est normal dans les systèmes distribués.

← Précédent
Proxmox local-lvm à 100 % : trouvez ce qui a mangé votre thin pool et réparez-le
Suivant →
Fail2ban pour le courrier : règles qui bloquent réellement les attaques

Laisser un commentaire