Vous surveillez les logs d’un hôte Debian 13 et voilà que ça réapparaît : write: broken pipe,
EPIPE, ou le classique Python BrokenPipeError: [Errno 32].
Parfois rien ne casse. Parfois les utilisateurs rafraîchissent une page blanche et votre rotation d’astreinte met à jour son CV.
« Broken pipe » est l’un de ces messages à la fois banal et très révélateur.
Cela peut signifier « le client s’est ennuyé et est parti » — ou « votre système était si lent que le client a abandonné, et maintenant votre backlog va déferler ».
L’astuce est de savoir dans quel cas vous êtes en cinq minutes, pas cinquante.
Ce que signifie réellement « Broken pipe » sur Debian 13
Sur Linux, « Broken pipe » est presque toujours l’erreur EPIPE (errno 32) renvoyée à un processus
qui tente d’écrire sur un pipe ou un socket dont le lecteur n’existe plus côté opposé.
Le lecteur peut être :
- Un autre processus dans un pipeline shell (
producer | consumer) qui est sorti prématurément. - Un pair TCP distant qui a fermé la connexion (parfois proprement avec FIN, parfois brusquement avec RST).
- Un proxy (Nginx, HAProxy) qui a fermé l’amont ou l’aval pendant que vous écriviez.
- Un client SSH qui a disparu à cause du Wi‑Fi, de la mise en veille ou d’un timeout d’inactivité.
Le comportement du noyau importe : pour les pipes et sockets, une écriture vers un pair déconnecté déclenche généralement
SIGPIPE. Beaucoup de programmes ne veulent pas mourir brutalement, ils ignorent ou gèrent SIGPIPE,
puis voient l’erreur EPIPE provenant de write(2) / send(2).
C’est pourquoi vous verrez des messages tels que :
write() failed (32: Broken pipe)sendfile() failed (32: Broken pipe)BrokenPipeError: [Errno 32] Broken pipe
La subtilité : « Broken pipe » n’est pas la cause racine. C’est un symptôme en aval.
La cause racine est « le lecteur est parti », et le lecteur est parti pour une raison. Parfois cette raison est normale.
Parfois c’est un goulet de performance qui va devenir un incident.
Une citation à retenir lors du triage : L’espoir n’est pas une stratégie.
— Général Gordon R. Sullivan.
« Broken pipe » vous dit que c’est le moment d’arrêter d’espérer et de commencer à mesurer.
Où vous le verrez sur Debian 13
Debian 13 est basé sur systemd, donc le principal témoin est journald. Vous verrez EPIPE dans :
- Les logs d’application (Python, Go, Java, Node, Rust) écrivant vers des sockets ou stdout/stderr.
- Les logs de serveurs web (Nginx/Apache) se plaignant de clients ou d’amonts.
- Les sessions SSH (logs côté serveur, avertissements côté client).
- Les scripts shell avec pipelines où un côté sort tôt (
head,grep -m). - Les outils de sauvegarde/transfert (rsync, tar over ssh, curl uploads) lorsque les pairs ferment les connexions.
Le message lui‑même est souvent exact mais peu utile. Votre travail est de décider :
est-ce une déconnexion attendue, ou un symptôme de latence, resets, surcharge ou mauvaise configuration ?
Feuille de route de diagnostic rapide (premier/deuxième/troisième)
Quand « Broken pipe » commence à apparaître et que quelqu’un demande « est‑ce grave ? », ne discutez pas. Limitez le temps.
Voici le chemin le plus rapide vers une réponse valable en postmortem.
Premier : confirmer la portée et le rayon d’impact (2 minutes)
- Est‑ce un hôte ou plusieurs ?
- Est‑ce un service ou tous les services qui communiquent en TCP ?
- Est‑ce corrélé avec des erreurs visibles par les utilisateurs (5xx, timeouts) ou juste du bruit de logs ?
Si c’est une seule application bruyante et que les utilisateurs vont bien, c’est probablement bénin (mais corrigez quand même le logging).
Si c’est plusieurs services ou la flotte entière, traitez‑le comme un avertissement précoce d’un problème systémique de latence ou réseau.
Deuxième : décider si c’est l’impatience du client ou la détresse du serveur (5 minutes)
- Vérifiez si vous observez des timeouts, une croissance des files d’attente ou une latence de requêtes élevée autour du même moment.
- Recherchez des resets TCP, des retransmissions ou un fort churn de connexions.
- Vérifiez des blocages de stockage si votre service écrit des logs, fait des uploads ou stream des données.
Troisième : isoler le lien défaillant (10 minutes)
- Déconnexions côté client (navigateur fermé, réseau mobile, timeout d’inactivité du load balancer).
- Comportement de proxy (buffering Nginx, keepalive amont, resets de flux HTTP/2).
- Pression au niveau du noyau/ressources (OOM, vol CPU, backlog de sockets, descripteurs de fichiers).
- Latence de stockage (sync journald, fsync massif, disques lents) provoquant des délais de traitement des requêtes.
Votre objectif n’est pas de « faire disparaître l’erreur ». Votre objectif est de répondre : qui a fermé en premier, et pourquoi ?
Bruit inoffensif vs premier avertissement : comment distinguer
Schémas généralement inoffensifs
Voici des cas courants et souvent acceptables :
- Sessions SSH interactives : l’ordinateur portable se met en veille, le Wi‑Fi bascule, NAT expire. Les logs serveur peuvent montrer broken pipe quand il tente d’écrire sur une session morte.
- Pipelines :
yes | head,journalctl | grep -m 1. Le consommateur sort tôt ; le producteur se plaint. - Clients qui annulent des téléchargements : l’utilisateur change de page ; votre serveur continue d’envoyer et reçoit EPIPE.
- Checks de santé et probes : certaines probes se connectent et se déconnectent rapidement, surtout si mal configurées.
Dans ces cas, la « correction » consiste généralement à supprimer les logs bruyants, gérer correctement SIGPIPE ou ajuster les timeouts.
N’allez pas chasser des fantômes à 3h du matin.
Schémas d’alerte (à surveiller)
Maintenant les cas pénibles :
- Pics soudains sur plusieurs services : souvent instabilité réseau, changement de proxy ou dépendance partagée en attente.
- Broken pipe associé à des timeouts / 499 / 502 / 504 : classique « le client a abandonné » alors que le serveur travaillait encore.
- Broken pipe pendant des uploads/streams : peut indiquer des problèmes MTU, perte de paquets, resets ou limites du load balancer.
- Corrélé avec du CPU iowait ou de la latence disque : le serveur est lent ; les clients se déconnectent ; vous voyez EPIPE en écrivant la réponse ou la sortie de log.
- Apparaît après des changements « d’optimisation » : ajustements de buffering, modifications de keepalive, timeouts agressifs, basculement d’offload TCP.
La frontière entre inoffensif et sérieux est généralement la corrélation.
Les broken pipes en eux‑mêmes ne vous font pas de mal ; ce sont les conditions qui les produisent qui posent problème.
Blague #1 : Broken pipe est la façon dont le système d’exploitation dit « ils ont raccroché ». C’est essentiellement du ghosting noyau.
Faits intéressants et contexte historique
- « Broken pipe » est plus ancien que TCP : il remonte aux premiers pipelines Unix, où un processus écrit et un autre lit.
SIGPIPEexiste pour arrêter les écrivains incontrôlés : sans lui, un programme pourrait continuer d’écrire indéfiniment dans le vide, gaspillant du CPU.- Le statut HTTP 499 est un indice : Nginx utilise 499 pour signifier « client fermé la requête », souvent associé à broken pipe côté serveur.
- EPIPE vs ECONNRESET est subtil : EPIPE signifie typiquement « vous avez écrit après que le pair ait fermé » ; ECONNRESET signifie souvent « le pair a reset en cours de flux ». Les deux peuvent apparaître selon le timing.
- Les proxies amplifient le symptôme : un client impatient unique derrière un proxy peut produire des broken pipes côté serveur qui ressemblent à des problèmes en amont.
- Le keepalive TCP n’est pas une panacée : il détecte les pairs morts lentement par défaut (heures), et beaucoup d’échecs sont dus à des middleboxes qui rendent l’hôte « vivant mais inatteignable ».
- Les tampons de pipe Linux ont évolué : tampons dynamiques plus grands réduisent la contention mais n’éliminent pas SIGPIPE/EPIPE lorsque les lecteurs disparaissent.
- Journald peut faire partie de l’histoire : si l’écriture des logs bloque à cause de la pression disque, les apps peuvent se bloquer suffisamment longtemps pour que les pairs se déconnectent, générant des broken pipes ailleurs.
- « Broken pipe » peut être un cas de réussite : des outils comme
headferment intentionnellement tôt. L’erreur du producteur est attendue et souvent ignorée.
Tâches pratiques : commandes, sorties, décisions (12+)
Voici les actions concrètes. Chaque tâche inclut une commande, ce que signifie une sortie typique, et la décision à en tirer.
Exécutez-les sur Debian 13 en root ou avec sudo quand nécessaire.
Task 1: Find the exact services emitting “broken pipe” (journald)
cr0x@server:~$ sudo journalctl -S -2h | grep -i -E 'broken pipe|EPIPE|SIGPIPE' | head -n 20
Dec 30 08:12:41 api-01 gunicorn[2198]: BrokenPipeError: [Errno 32] Broken pipe
Dec 30 08:12:42 web-01 nginx[1120]: *18452 sendfile() failed (32: Broken pipe) while sending response to client
Dec 30 08:12:43 api-01 gunicorn[2198]: Ignoring EPIPE in worker
Ce que cela signifie : vous avez au moins deux sources : Gunicorn (couche application) et Nginx (edge/proxy).
Décision : priorisez la couche la plus proche des utilisateurs d’abord (Nginx). Si Nginx casse, il s’agit peut‑être de clients qui partent ou d’un amont qui se bloque.
Task 2: Quantify the spike over time (quick histogram)
cr0x@server:~$ sudo journalctl -S -2h -o short-iso | grep -i 'broken pipe' | awk '{print substr($1,1,16)}' | sort | uniq -c | tail
12 2025-12-30T08:05
18 2025-12-30T08:06
61 2025-12-30T08:07
59 2025-12-30T08:08
14 2025-12-30T08:09
Ce que cela signifie : un pic entre 08:07–08:08. Ce n’est pas du bruit aléatoire.
Décision : alignez cette fenêtre avec la charge, les déploiements, les événements réseau ou la latence de stockage.
Task 3: Check Nginx for client-closed requests (499) and upstream issues (5xx)
cr0x@server:~$ sudo awk '$9 ~ /^(499|502|504)$/ {print $4, $9, $7}' /var/log/nginx/access.log | tail -n 10
[30/Dec/2025:08:07:12 499 /api/v1/report
[30/Dec/2025:08:07:13 499 /api/v1/report
[30/Dec/2025:08:07:15 504 /api/v1/report
[30/Dec/2025:08:07:18 502 /api/v1/report
Ce que cela signifie : les clients ferment (499) et vous avez aussi des erreurs passerelle (502/504).
Décision : traitez‑le comme réel. Enquêtez la latence en amont et les resets réseau. Les 499 suivent souvent des amonts lents.
Task 4: Inspect Nginx error log context around EPIPE
cr0x@server:~$ sudo grep -n -i 'broken pipe' /var/log/nginx/error.log | tail -n 5
183271:2025/12/30 08:07:12 [info] 1120#1120: *18452 sendfile() failed (32: Broken pipe) while sending response to client, client: 203.0.113.41, server: _, request: "GET /api/v1/report HTTP/1.1", host: "example"
183289:2025/12/30 08:07:18 [error] 1120#1120: *18488 upstream prematurely closed connection while reading response header from upstream, client: 203.0.113.55, server: _, request: "GET /api/v1/report HTTP/1.1", upstream: "http://10.0.2.20:8000/api/v1/report"
Ce que cela signifie : vous avez à la fois des broken pipes en aval (client parti) et une instabilité en amont (amont fermé tôt).
Décision : allez en amont : santé du serveur d’applications, timeouts, plantages de workers ou pression sur les ressources.
Task 5: Check system load and iowait (is this a stall story?)
cr0x@server:~$ uptime
08:10:22 up 41 days, 6:12, 2 users, load average: 12.48, 11.97, 10.55
cr0x@server:~$ mpstat 1 5
Linux 6.12.0-amd64 (api-01) 12/30/2025 _x86_64_ (16 CPU)
08:10:27 AM all %usr %nice %sys %iowait %irq %soft %steal %idle
08:10:28 AM all 22.1 0.0 9.8 31.4 0.0 1.2 0.0 35.5
Ce que cela signifie : forte charge avec un iowait important. Les CPUs attendent le stockage.
Décision : arrêtez d’accuser le « réseau » tant que vous n’avez pas écarté la latence disque et les problèmes d’écriture/logging/fsync.
Task 6: Identify top I/O offenders (per-process view)
cr0x@server:~$ sudo iotop -o -b -n 3
Total DISK READ: 8.42 M/s | Total DISK WRITE: 52.11 M/s
PID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
2198 be/4 www-data 0.00 B/s 9.14 M/s 0.00 % 42.13 % gunicorn: worker [api]
786 be/3 root 0.00 B/s 7.32 M/s 0.00 % 18.55 % systemd-journald
3011 be/4 postgres 0.00 B/s 16.44 M/s 0.00 % 22.01 % postgres: checkpointer
Ce que cela signifie : journald et le checkpointing de la base écrivent beaucoup ; l’app écrit aussi.
Décision : corrélez avec les rafales ; envisagez de limiter le volume de logs, déplacer les logs vers un stockage plus rapide, ou régler les checkpoints DB.
Task 7: Check disk latency and saturation
cr0x@server:~$ iostat -xz 1 3
Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await %util
nvme0n1 9.20 421.3 0.36 52.10 252.1 18.44 41.2 8.7 42.1 99.2
Ce que cela signifie : le périphérique est à ~99% d’utilisation avec des await d’écriture à 40 ms — pas catastrophique, mais suffisant pour pousser la latence des requêtes au‑delà des timeouts client/proxy.
Décision : traitez le stockage comme suspect principal. « Broken pipe » peut être le premier symptôme visible d’écritures lentes.
Task 8: Confirm whether TCP resets are happening (kernel counters)
cr0x@server:~$ nstat -az | egrep 'TcpExtTCPRcvCoalesce|TcpExtListenOverflows|TcpExtListenDrops|TcpAbortOnTimeout|TcpAbortOnData|TcpEstabResets|TcpOutRsts'
TcpExtListenOverflows 0
TcpExtListenDrops 0
TcpAbortOnTimeout 37
TcpAbortOnData 0
TcpEstabResets 91
TcpOutRsts 148
Ce que cela signifie : aborts sur timeout et resets sont non nuls et peuvent augmenter.
Décision : si ces compteurs montent pendant la même fenêtre que les broken pipes, enquêtez le chemin réseau et les timeouts applicatifs. Vérifiez aussi conntrack/behaviour du LB.
Task 9: Inspect active connections and churn
cr0x@server:~$ ss -s
Total: 2817 (kernel 0)
TCP: 2149 (estab 327, closed 1571, orphaned 0, timewait 1418)
Transport Total IP IPv6
RAW 0 0 0
UDP 19 14 5
TCP 578 438 140
INET 597 452 145
FRAG 0 0 0
Ce que cela signifie : beaucoup de connexions fermées/en timewait ; c’est du churn. Pas automatiquement mauvais, mais c’est un signal si c’est soudain.
Décision : si vous attendiez des keepalives/réutilisation et que vous observez maintenant du churn, revoyez les settings keepalive du proxy, le comportement client, et les changements TLS/HTTP2 récents.
Task 10: Verify file descriptor pressure (classic silent killer)
cr0x@server:~$ cat /proc/sys/fs/file-nr
4128 0 9223372036854775807
cr0x@server:~$ sudo lsof -p 1120 2>/dev/null | wc -l
1832
Ce que cela signifie : pas d’épuisement global des FD, mais Nginx a beaucoup d’ouverts. Si les limites par process sont basses, vous verriez des refus d’accept.
Décision : si vous voyez too many open files avec EPIPE, corrigez les ulimits et la réutilisation des connexions. Sinon, poursuivez.
Task 11: Confirm whether shell pipeline EPIPE is just a pipeline being a pipeline
cr0x@server:~$ yes "spam" | head -n 1
spam
yes: standard output: Broken pipe
Ce que cela signifie : totalement normal : head sort après une ligne, yes continue d’écrire et reçoit EPIPE.
Décision : si votre monitoring signale ce type de sortie comme un « incident », corrigez le monitoring. Ne corrigez pas Unix.
Task 12: Debug SSH “broken pipe” (keepalive and idle timeout)
cr0x@server:~$ grep -n -E 'ClientAliveInterval|ClientAliveCountMax' /etc/ssh/sshd_config
124:ClientAliveInterval 60
125:ClientAliveCountMax 3
cr0x@server:~$ sudo journalctl -u ssh -S -2h | tail -n 5
Dec 30 08:02:10 bastion-01 sshd[18812]: packet_write_wait: Connection to 198.51.100.23 port 53722: Broken pipe
Ce que cela signifie : le serveur tente d’écrire sur un client mort. Des settings keepalive existent ; des middleboxes peuvent toujours couper des sessions inactives.
Décision : si SSH est critique pour l’activité, configurez à la fois ClientAlive* côté serveur et ServerAliveInterval côté client. Si c’est occasionnel, acceptez‑le comme une contrainte physique.
Task 13: Trace which process is throwing SIGPIPE/EPIPE (strace on a PID)
cr0x@server:~$ sudo strace -p 2198 -f -e trace=write,sendto,sendmsg -s 80
strace: Process 2198 attached
sendto(17, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n...", 4096, MSG_NOSIGNAL, NULL, 0) = -1 EPIPE (Broken pipe)
Ce que cela signifie : l’application écrit une réponse et le pair est parti. MSG_NOSIGNAL indique que l’app évite SIGPIPE et gère EPIPE à la place.
Décision : déterminez si le pair est Nginx (socket amont) ou un client direct. Ensuite vérifiez timeouts et buffering en amont.
Task 14: Check for kernel-level out-of-memory kills (which can look like “upstream closed”)
cr0x@server:~$ sudo journalctl -k -S -2h | grep -i -E 'oom|killed process' | tail -n 10
Dec 30 08:07:16 api-01 kernel: Out of memory: Killed process 2241 (gunicorn) total-vm:812344kB, anon-rss:312144kB, file-rss:0kB, shmem-rss:0kB
Ce que cela signifie : votre amont est mort, et Nginx rapportera une fermeture/reset d’amont ; les clients verront broken pipes/timeouts.
Décision : c’est une cause d’incident, pas un symptôme. Corrigez les limites mémoire, les fuites ou la concurrence. Ne modifiez pas Nginx pour « masquer » le problème.
Task 15: Validate journald pressure and rate limiting
cr0x@server:~$ sudo journalctl --disk-usage
Archived and active journals take up 3.8G in the file system.
cr0x@server:~$ sudo grep -n -E 'RateLimitIntervalSec|RateLimitBurst|SystemMaxUse' /etc/systemd/journald.conf
19:RateLimitIntervalSec=30s
20:RateLimitBurst=10000
33:SystemMaxUse=2G
Ce que cela signifie : l’usage disque de journald est au‑dessus du maximum configuré (peut‑être que la config n’a pas été rechargée, ou qu’il y a plusieurs emplacements de journaux).
Décision : si journald thrash le disque, réduisez les logs verbeux, ajustez les limites et redémarrez journald pendant une fenêtre de maintenance. Le logging ne doit pas faire tomber votre API.
Trois mini-récits d’entreprise issus du terrain
Mini-récit 1 : L’incident causé par une mauvaise hypothèse (« broken pipe signifie que le client est capricieux »)
Une entreprise moyenne exploitait une flotte Debian derrière Nginx. Leur playbook d’astreinte traitait « broken pipe » comme « utilisateurs mobiles en mouvement ».
Cela était vrai pour leur application grand public — jusqu’à ce qu’ils déploient une API partenaire utilisée par des systèmes backend, pas des humains.
Un mardi, les logs se sont enflammés de broken pipes. L’astreinte a haussé les épaules : « les partenaires ont un réseau bof aussi ».
Mais leur partenaire n’était pas sur un téléphone en ascenseur. C’était un service de centre de données appelant via un interconnect privé.
Le partenaire a ouvert un ticket : « timeouts, réponses partielles ».
Le vrai problème était leur propre amont : une nouvelle fonctionnalité ajoutait la génération synchrone de PDF dans le chemin de requête.
Sous charge, le CPU a grimpé, les écritures disque (fichiers temporaires) ont explosé, et la latence a dépassé le timeout de 10 secondes du partenaire.
Le partenaire a fermé la connexion ; Nginx a continué d’essayer d’écrire les réponses et a rapporté EPIPE.
Le postmortem a retenu : « client fermé » n’est pas une excuse ; c’est un indice.
Si une classe cohérente de clients devient subitement « instable », le serveur a changé — ou il a tellement ralenti que le réseau en paraît responsable.
Correctif : ils ont déplacé la génération de PDF vers une file d’attente en arrière‑plan, renvoyé un ID de job, et resserré les timeouts Nginx pour échouer rapidement avec des erreurs explicites au lieu d’un lent dégoulinement vers des broken pipes.
Mini-récit 2 : L’optimisation qui s’est retournée contre eux (buffering et timeouts)
Une autre équipe voulait réduire la latence pour les réponses en flux. Ils ont réduit le buffering proxy Nginx et augmenté la réutilisation des keepalive.
Sur le papier, cela signifiait « moins de mémoire, premier octet plus rapide, moins de connexions ». En pratique, cela signifiait « plus d’exposition aux comportements lents en amont ».
Avec moins de buffering, Nginx a commencé à transmettre immédiatement les chunks amont aux clients.
Quand des handlers amont se sont bloqués en plein milieu d’une réponse (une requête DB attendant un verrou), les clients sont restés avec un socket ouvert.
Leur load balancer avait un timeout d’inactivité plus court que les réponses les plus lentes.
Le LB a donc fermé la connexion client. Nginx a essayé de continuer à envoyer et a logué des broken pipes.
Pire : l’amont a continué à exécuter la requête, consommant CPU et tenant des connexions DB même si le client était déjà parti.
Ils ont essentiellement payé le prix fort pour des requêtes que personne n’allait voir.
Leur première réaction a été d’augmenter les timeouts partout. Cela a rendu les logs « broken pipe » plus calmes mais a fragilisé la plateforme :
les requêtes lentes ont duré plus longtemps, les files d’attente ont grossi et la latence des queues s’est dégradée. Ils ont échangé un symptôme bruyant contre un rayon d’impact plus large.
Correctif : ils ont rétabli un buffering raisonnable pour cet endpoint, ajouté l’annulation côté serveur quand possible,
et aligné volontairement les timeouts (client < LB < Nginx < amont) avec des budgets explicites.
Les « broken pipe » ont diminué parce que le système a cessé de faire du travail inutile après le départ du client.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (corrélation et baselines)
Une organisation plus mûre avait une habitude simple : chaque passage de relais d’astreinte incluait une capture « baseline hebdomadaire » de latence, taux d’erreur,
resets TCP et await disque. Pas de ML, juste une forme connue.
Quand les broken pipes ont augmenté un jeudi après‑midi, ils n’ont pas débattu pour savoir si c’était « normal ».
Ils ont sorti la baseline : les resets TCP étaient stables, mais l’await disque avait doublé et le débit d’écriture de journald avait explosé.
Cela a immédiatement recadré l’incident de « réseau » vers « stockage/logging ».
Le coupable était un flag de debug activé accidentellement en production sur un endpoint API très fréquenté.
Il a augmenté le volume de logs suffisamment pour saturer le disque pendant des minutes. Les handlers de requêtes ont bloqué sur les écritures de log,
les clients ont expiré, et Nginx a observé des broken pipes.
Le correctif était ennuyeux : désactiver les logs de debug, limiter les rafales de logs, et envoyer les logs verbeux vers un nœud dédié quand nécessaire.
Ils avaient aussi une modification pré‑approuvée pour augmenter les IOPS de la partition de logs sur leur stockage virtualisé, utilisée comme soupape temporaire.
Pas de héros, pas de « restart everything ». Juste un diagnostic rapide et correct parce qu’ils savaient ce à quoi ressemblait le « normal ».
Erreurs courantes : symptôme → cause racine → fix
1) « Broken pipe » dans le log d’erreur Nginx lors de téléchargements
- Symptôme :
sendfile() failed (32: Broken pipe) while sending response to client - Cause racine : le client a fermé la connexion (changement de page, annulation app, timeout LB).
- Correctif : traitez comme informationnel sauf si corrélé avec des pics 499/5xx. Si bruyant, baissez le niveau de log pour ce message ou ajustez l’échantillonnage des access logs. Ne désactivez pas globalement des logs utiles.
2) Pics de broken pipes après activation de gzip ou de grosses réponses
- Symptôme : plus d’EPIPE sur endpoints à gros payload, plus temps de réponse augmenté.
- Cause racine : saturation CPU ou lenteur amont augmentant le temps jusqu’au dernier octet ; clients/LB abandonnent.
- Correctif : benchmarkez la compression, limitez la taille des payloads, utilisez du caching, ou déplacez la génération lourde hors requête. Alignez les timeouts et envisagez du buffering.
3) « upstream prematurely closed connection » plus broken pipe
- Symptôme : Nginx montre un amont fermé prématurément ; les clients voient 502 ; les logs montrent aussi broken pipe.
- Cause racine : l’amont a planté/redémarré, a été OOM‑killed, ou a atteint un timeout interne et a fermé le socket.
- Correctif : vérifiez les logs kern OOM, les logs de plantage d’app, et les redémarrages de processus. Corrigez la mémoire, la concurrence et les health checks. Ne masquez pas avec des timeouts proxy plus longs.
4) Sessions SSH se terminent fréquemment par « Broken pipe »
- Symptôme : logs serveur
packet_write_wait ... Broken pipe, utilisateurs se plaignent de sessions coupées. - Cause racine : timeouts d’inactivité dans NAT/LB/firewall, mise en veille d’ordinateur, Wi‑Fi instable.
- Correctif : configurez
ServerAliveIntervalcôté client etClientAliveIntervalcôté serveur. Si derrière un bastion/LB, alignez les timeouts d’inactivité.
5) Application Python plante avec BrokenPipeError en écrivant vers stdout
- Symptôme : l’app sort pendant du logging ou du print ; la stack trace se termine par
BrokenPipeError. - Cause racine : stdout est pipé vers un processus qui est sorti (crash du log shipper,
headou pipe fermé du gestionnaire de service). - Correctif : gérez SIGPIPE/EPIPE, utilisez des handlers de logging appropriés, et assurez la résilience des collecteurs de logs. Dans des services systemd, envisagez le logging direct vers journald plutôt que des pipes fragiles.
6) « Broken pipe » pendant rsync/tar over ssh
- Symptôme : le transfert s’arrête ; rsync signale broken pipe ; des fichiers partiels restent.
- Cause racine : interruption réseau, mismatch de keepalive SSH, ou stall disque distant provoquant un timeout et la déconnexion.
- Correctif : utilisez des keepalives, options rsync résumables, et vérifiez la latence disque des deux côtés. Évitez les gros transferts de fichiers uniques sans stratégie de reprise.
7) Beaucoup de broken pipes pendant les déploiements, mais seulement pendant une minute
- Symptôme : tempête brève d’EPIPE autour du déploiement.
- Cause racine : redémarrage d’amont ferme les connexions keepalive en plein vol ; les clients voient des déconnexions.
- Correctif : effectuez des reloads gracieux, drenez les connexions, ajustez les readiness checks et étalez les redémarrages. Informez le proxy de la santé des amonts.
8) Broken pipes coïncident avec un iowait élevé
- Symptôme : pics d’EPIPE, hausse des 499, iowait élevé, %util disque proche de 100.
- Cause racine : le goulot de stockage retarde le traitement des requêtes ; les clients expirent et se déconnectent ; le serveur écrit alors dans des sockets fermés.
- Correctif : réduisez les écritures synchrones dans le chemin de requête, déplacez les logs vers un stockage plus rapide, ajustez les checkpoints DB, et corrigez le goulot disque sous‑jacent.
Blague #2 : Si vous « corrigez » broken pipe en réduisant les logs, vous n’avez pas réparé le pipe — vous lui avez juste retiré sa capacité à se plaindre.
Listes de contrôle / plan étape par étape
Plan de triage étape par étape (15–30 minutes)
- Confirmez que c’est réel : corrélez les messages EPIPE avec les métriques visibles par les utilisateurs (HTTP 5xx, latence, timeouts). Si rien ne corrèle, traitez comme du bruit et planifiez un nettoyage.
- Identifiez la couche : Nginx/Apache vs serveur d’app vs SSH vs scripts. Causes différentes, corrections différentes.
- Classez la direction : aval (client parti) vs amont (backend parti). Le log d’erreur Nginx indique généralement.
- Vérifiez l’alignement des timeouts : timeout client < timeout LB < proxy read/send < amont. Les désalignements causent du churn et des broken pipes.
- Vérifiez la pression sur les ressources : saturation CPU, iowait, kills OOM, limites de FD, problèmes de backlog socket.
- Vérifiez les symptômes réseau : resets, retransmissions, pression conntrack, mismatches MTU (si isolé à certains chemins).
- Vérifiez la latence stockage : iostat await/%util, iotop top writers, usage disque de journald, orages de checkpoints DB.
- Confirmez avec une trace ciblée : strace sur un process ou capture tcpdump courte si nécessaire (prudent en production).
- Faites un changement à la fois : timeouts, buffering, volume de logs, ou scaling. Puis re‑mesurez.
Règles strictes pour les systèmes de production
- Ne traitez jamais « client closed » comme « pas notre problème » avant d’avoir vérifié que le client ne ferme pas à cause de votre latence.
- Ne prolongez pas les timeouts pour masquer des symptômes. Des timeouts plus longs augmentent souvent la concurrence et étendent les modes de défaillance.
- Le logging est une charge de travail. Si vous ne pouvez pas assumer votre volume de logs lors de vos pires jours, vous ne pouvez pas l’assumer du tout.
- Connaissez vos budgets. Si votre LB tue les connexions inactives à 60s, ne laissez pas les appels amont prendre 75s en prétendant que c’est « résilient ».
FAQ
1) « Broken pipe » est‑ce toujours une erreur ?
Non. Dans les pipelines, c’est souvent attendu. Dans les services réseau, c’est un symptôme qu’un pair a fermé ; si c’est grave dépend de la corrélation avec des échecs et la latence.
2) Quelle est la différence entre EPIPE et ECONNRESET ?
EPIPE signifie généralement que vous avez écrit après que le pair ait fermé. ECONNRESET signifie souvent que le pair a envoyé un RST TCP, tuant abruptement la connexion. Les deux peuvent survenir dans des scénarios similaires ; le timing décide lequel vous voyez.
3) Pourquoi je vois « broken pipe » quand les utilisateurs annulent des téléchargements ?
Le serveur continue d’écrire la réponse jusqu’à ce qu’il constate que le client est parti. L’écriture suivante échoue avec EPIPE. C’est normal pour les gros téléchargements et le streaming.
4) Nginx affiche 499 et broken pipe. Qui est en faute ?
499 signifie que le client a fermé la requête. Mais les clients ferment pour des raisons : amont lent, timeouts LB, réseaux mobiles. Si les 499 augmentent avec la latence amont, traitez‑le comme un problème côté serveur.
5) La latence de stockage peut‑elle vraiment causer des broken pipe sur un serveur web ?
Oui. Si les handlers de requête bloquent sur le disque (logging, fichiers temporaires, écritures DB), la latence de réponse augmente. Les clients et proxies expirent et ferment. Votre écriture suivante tombe sur EPIPE.
6) Pourquoi je vois broken pipe dans SSH alors que le serveur va bien ?
Les sessions SSH meurent lorsque NAT/firewalls suppriment l’état inactif ou qu’un portable se met en veille. Le serveur l’apprend seulement quand il écrit et reçoit EPIPE. Les keepalives aident, mais ne battent pas toutes les politiques de middlebox.
7) Dois‑je ignorer SIGPIPE dans mon application ?
Souvent oui, mais de façon délibérée. Beaucoup de serveurs réseau suppriment SIGPIPE et gèrent les erreurs EPIPE à la place. La bonne stratégie dépend de votre langage/runtime et de la possibilité de retenter en toute sécurité.
8) Nous avons changé les paramètres keepalive et maintenant les broken pipes ont augmenté. Pourquoi ?
Keepalive peut augmenter la réutilisation, mais il augmente aussi la probabilité d’écrire sur une connexion qu’une middlebox a silencieusement coupée. Si le pair a disparu sans fermeture propre, votre écriture suivante révèle le problème.
9) Comment faire pour que les broken pipes ne remplissent pas les logs sans masquer les vrais problèmes ?
Réduisez la verbosité des logs pour les déconnexions attendues en bordure (par ex. aborts clients courants), mais conservez métriques et compteurs d’erreurs. L’objectif est d’améliorer le rapport signal/bruit, pas de devenir aveugle.
10) Debian 13 change‑t‑il quelque chose au comportement de broken pipe ?
Les sémantiques de base sont au niveau noyau et durent depuis longtemps. Ce qui change en pratique, c’est votre stack : systemd/journald plus récent, noyaux plus récents, et des valeurs par défaut différentes dans des paquets comme OpenSSH et Nginx.
Conclusion : prochaines étapes actionnables
« Broken pipe » sur Debian 13 est une lampe torche, pas un verdict. Parfois elle éclaire la vérité ennuyeuse : le client est parti, le pipeline s’est terminé, la vie continue.
Parfois c’est la première fissure visible d’un problème plus profond : stalls de stockage, timeouts mal alignés, crashes d’amont, ou churn réseau.
Prochaines étapes qui rapportent immédiatement :
- Construisez une habitude de corrélation rapide : les pics de broken pipe doivent toujours être vérifiés par rapport à la latence et aux taux 499/5xx.
- Alignez volontairement les timeouts entre client/LB/proxy/upstream, et documentez‑les sérieusement.
- Mesurez la latence de stockage (await/%util) quand vous voyez des broken pipes sous charge ; ne supposez pas que c’est « le réseau ».
- Traitez le logging comme une caractéristique de performance : limitez les logs debug, gardez journald sain, et évitez les écritures synchrones sur les chemins chauds.
- Conservez une capture « known‑good baseline » pour vos services cœur. Elle transforme une suspicion vague en diagnostic rapide.