Problèmes MTU sur les réseaux Docker : pourquoi les grosses requêtes échouent et comment corriger MTU/MSS

Cet article vous a aidé ?

Tout « fonctionne » jusqu’à ce que ça ne fonctionne plus. Les pings petits réussissent, les contrôles de santé sont verts, les pages de connexion s’affichent, puis survient une vraie requête : un POST JSON de 2 Mo, une négociation TLS avec une chaîne de certificats lourde, un stream gRPC, un conteneur téléchargeant une couche. Soudain : blocages, timeouts, réessais mystérieux, ou ce classique message Slack d’entreprise : « Le réseau est lent ? »

Si cela n’arrive que dans Docker (ou « seulement depuis les conteneurs », ou « seulement via le VPN », ou « seulement quand le trafic traverse l’overlay »), vous regardez probablement un problème MTU/MSS. Les problèmes MTU font douter les personnes expérimentées. Les symptômes sont sélectifs, l’observabilité est faible par défaut, et les correctifs sont étonnamment faciles à rater.

MTU, MSS, fragmentation, PMTUD : ce qui casse réellement

MTU en une phrase

MTU (Maximum Transmission Unit) est la plus grande taille de paquet IP (en octets) qui peut traverser un lien sans être fragmentée sur ce saut.

Et le MSS est la pièce que l’on oublie

MSS TCP (Maximum Segment Size) est la plus grande charge utile TCP qu’un hôte mettra dans un segment TCP ; il est dérivé du MTU du chemin moins les en-têtes IP et TCP. En Ethernet/IP/TCP sans options, un MTU de 1500 donne couramment un MSS de 1460 octets (1500 – 20 – 20).

Le MTU concerne les paquets sur le câble. Le MSS concerne la taille des blocs que TCP découpe. Si vous diminuez le MSS, vous pouvez éviter la fragmentation en garantissant que les paquets sont assez petits pour tenir dans le plus petit MTU du chemin.

Pourquoi les petites requêtes fonctionnent mais les grosses échouent

Avec un décalage MTU, vous pouvez obtenir une « défaillance sélective » :

  • Les petites charges tiennent dans de petits paquets ; elles passent.
  • Les grosses charges génèrent des paquets qui dépassent le MTU de certains sauts ; ils nécessitent une fragmentation ou un MSS plus petit.
  • Si la fragmentation est bloquée ou que la Path MTU Discovery (PMTUD) est cassée, ces gros paquets tombent dans un trou noir.

Fragmentation, DF et le motif du trou noir

IPv4 peut fragmenter les paquets en transit. Mais les piles modernes essaient d’éviter cela. Elles positionnent le bit DF (Don’t Fragment) et comptent sur la PMTUD : l’émetteur envoie des paquets DF et attend que le réseau lui indique le MTU maximum en renvoyant des messages ICMP « Fragmentation needed » quand un paquet est trop gros.

Quand ces messages ICMP sont filtrés (groupes de sécurité, pare-feux, appareils réseau « utiles », ou politiques mal configurées), la PMTUD échoue silencieusement. L’émetteur continue de retransmettre des paquets qui ne passeront jamais. Pour l’application, cela ressemble à un blocage : la connexion TCP est établie, peut-être que quelques données passent, puis tout s’arrête.

Une citation à garder sur un post-it :
Idée paraphrasée : « L’espoir n’est pas une stratégie. » — attribuée dans les cercles SRE à la culture opérationnelle ; traitez le MTU comme un paramètre de conception, pas comme un vœu.

Docker facilite la création de décalages MTU

Docker ajoute des couches : bridges, paires veth, NAT, et parfois des overlays (VXLAN) ou des tunnels (WireGuard, IPsec, client VPN). Chaque encapsulation consomme des octets. En manger assez transforme votre « 1500 » en « 1450 », « 1412 » ou « 1376 » en pratique. Si seuls certains nœuds ou certaines routes ont ce surcoût, félicitations : vous avez un trou noir partiel.

Joke #1 : les bugs MTU sont comme les imprimantes de bureau — ils attendent que vous soyez en retard, puis ils développent du « caractère ».

Où Docker cache les problèmes MTU

La topologie habituelle

Sur un hôte Linux typique avec le réseau bridge par défaut de Docker :

  • Un conteneur se connecte via une paire veth à docker0 (un bridge Linux).
  • L’hôte route/NAT le trafic vers une interface physique comme eth0 ou ens5.
  • De là, il peut traverser VLANs, fabrics de cloud, VPNs, proxies et autres inventions réseau d’entreprise.

Le conteneur voit une MTU d’interface (souvent 1500). Le bridge voit une MTU (souvent 1500). La NIC hôte peut être 1500. Mais si le chemin réel inclut de l’encapsulation (par ex. un VPN ajoute ~60–80 octets, VXLAN ajoute ~50 octets, GRE ajoute ~24 octets, plus d’éventuels en-têtes supplémentaires), la MTU effective est plus petite. Si personne n’avertit les piles TCP, les segments larges seront trop grands.

Les réseaux overlay pimentent la situation

L’overlay Docker Swarm et de nombreux plugins CNI s’appuient sur VXLAN ou une encapsulation similaire pour reconstituer une sémantique L2 sur des réseaux L3. VXLAN ajoute typiquement 50 octets d’overhead (Ethernet externe + IP externe + UDP + en-têtes VXLAN ; l’overhead exact dépend de l’environnement). Si l’underlay est en 1500, l’overlay MTU devrait être autour de 1450.

Des problèmes surviennent lorsque :

  • Certains nœuds définissent l’overlay MTU à 1450, d’autres le laissent à 1500.
  • L’underlay n’est pas vraiment 1500 (les fabrics cloud varient ; les VPN varient encore plus).
  • ICMP est filtré entre nœuds, cassant la PMTUD.

Pourquoi vous avez l’impression que « TLS est instable » ou que « les POSTs bloquent »

TLS peut être un testeur MTU accidentel parce que certificats et flights de handshake peuvent être plus volumineux que prévu. Il en va de même pour les métadonnées gRPC, les trames HTTP/2, et le JSON « petit » qui est en fait énorme quand quelqu’un y colle un blob base64.

Un symptôme classique : la poignée de main TCP se termine, de petits en-têtes de réponse passent, puis la connexion se fige quand le premier gros segment avec DF posé atteint un saut MTU trop petit. Des retransmissions ont lieu. Finalement, vous obtenez un timeout.

Faits intéressants et un peu d’histoire

  • Fait 1 : le MTU Ethernet de 1500 octets est devenu un standard de facto surtout pour des compromis de conception historiques, pas parce que 1500 soit sacré.
  • Fait 2 : la Path MTU Discovery a été introduite parce que la fragmentation est coûteuse et fragile ; elle déplace le travail vers les points d’extrémité.
  • Fait 3 : la PMTUD dépend d’ICMP. Bloquer tout ICMP revient à enlever les panneaux de signalisation et ensuite reprocher aux conducteurs de s’être perdus.
  • Fait 4 : l’overhead d’encapsulation VXLAN pousse souvent la MTU d’overlay à ~1450 sur un underlay 1500 ; si vous gardez 1500, vous comptez sur la fragmentation ou les jumbo frames.
  • Fait 5 : les jumbo frames (MTU 9000) peuvent réduire la charge CPU dans certains cas, mais un seul saut limité à 1500 ruine tout sauf si vous clamez ou segmentez correctement.
  • Fait 6 : les routeurs IPv6 ne fragmentent pas en transit. Si vous cassez la PMTUD en IPv6, vous n’obtenez pas du « parfois ». Vous obtenez « ça ne marche pas ».
  • Fait 7 : le MSS TCP est négocié par direction pendant SYN/SYN-ACK ; les middleboxes peuvent (et souvent devraient) le réécrire à la volée pour les tunnels.
  • Fait 8 : de nombreux timeouts « aléatoires » d’API imputés aux serveurs applicatifs sont en fait des tempêtes de retransmissions dues à une PMTUD blackholée.
  • Fait 9 : le bridge par défaut de Docker et les dispositifs veth héritent généralement du MTU de l’hôte au moment de la création ; changer le MTU de l’hôte plus tard ne met pas toujours à jour les veth existants.

Playbook de diagnostic rapide

Voici l’ordre qui trouve le coupable le plus rapidement en production, avec le moins de détours.

1) Confirmer que c’est dépendant de la taille et du chemin

  • Depuis un conteneur, lancez une petite requête et une grosse vers la même destination.
  • Si la petite fonctionne et la grosse bloque/timeout, suspectez immédiatement le MTU.

2) Trouver la MTU de chemin effective avec des pings DF

  • Utilisez ping -M do -s pour faire une recherche binaire de la charge utile maximale qui passe.
  • Comparez les résultats depuis l’hôte et depuis le conteneur. Les différences comptent.

3) Inspecter les valeurs MTU sur toutes les interfaces pertinentes

  • NIC physique hôte, bridge Docker, veth, overlays, interface VPN/tunnel.
  • Si une interface de tunnel affiche 1420 et que les conteneurs croient avoir 1500, vous avez probablement trouvé.

4) Valider que la PMTUD n’est pas bloquée

  • Vérifiez les règles de pare-feu pour ICMP type 3 code 4 (IPv4) et ICMPv6 Packet Too Big (type 2).
  • Capturez le trafic : des erreurs ICMP reviennent-elles ? Y a-t-il des retransmissions répétées ?

5) Choisir votre stratégie de correction

  • Idéal : définir le MTU correct de bout en bout (underlay, overlay, conteneurs).
  • Pragmatique : clamer le MSS TCP à l’entrée/sortie d’un tunnel ou d’un bridge.
  • Éviter : « baisser partout sans comprendre » ; vous masquerez le souci et pourriez réduire les débits.

Tâches pratiques : commandes, sorties attendues et décisions

Ce sont des tâches de niveau production : ce que vous lancez, ce que vous espérez voir, ce que signifie une absence de résultat, et la suite. Exécutez-les sur un hôte Linux avec Docker sauf indication contraire.

Tâche 1 : Reproduire depuis l’intérieur d’un conteneur avec petite vs grosse charge

cr0x@server:~$ docker run --rm curlimages/curl:8.6.0 curl -sS -o /dev/null -w "%{http_code}\n" http://10.20.30.40:8080/health
200

Sens : la connectivité de base fonctionne.
Décision : testez maintenant une grosse requête qui génère de grands segments TCP.

cr0x@server:~$ docker run --rm curlimages/curl:8.6.0 sh -lc 'dd if=/dev/zero bs=1k count=2048 2>/dev/null | curl -sS -m 10 -o /dev/null -w "%{http_code}\n" -X POST --data-binary @- http://10.20.30.40:8080/upload'
curl: (28) Operation timed out after 10000 milliseconds with 0 bytes received

Sens : le schéma d’échec dépendant de la taille est présent.
Décision : arrêtez d’incriminer l’application tant que vous n’avez pas prouvé que les paquets traversent le chemin.

Tâche 2 : Comparer le comportement hôte vs conteneur

cr0x@server:~$ dd if=/dev/zero bs=1k count=2048 2>/dev/null | curl -sS -m 10 -o /dev/null -w "%{http_code}\n" -X POST --data-binary @- http://10.20.30.40:8080/upload
200

Sens : le chemin depuis l’hôte fonctionne mais celui depuis le conteneur échoue.
Décision : concentrez-vous sur le bridge Docker/veth/iptables et l’héritage MTU plutôt que uniquement sur le réseau en amont.

Tâche 3 : Vérifier le MTU sur la NIC hôte et le bridge Docker

cr0x@server:~$ ip -br link show dev eth0
eth0             UP             10.0.0.15/24 fe80::a00:27ff:feaa:bbbb/64 mtu 1500
cr0x@server:~$ ip -br link show dev docker0
docker0          UP             172.17.0.1/16 fe80::42:1cff:fe11:2222/64 mtu 1500

Sens : les deux sont à 1500, ce qui est « normal » mais pas forcément correct pour votre chemin réel.
Décision : identifiez les tunnels/overlays. Si présents, 1500 peut être un leurre.

Tâche 4 : Inspecter le MTU à l’intérieur d’un conteneur en cours

cr0x@server:~$ docker run --rm --network bridge alpine:3.19 ip -br link show dev eth0
eth0@if8         UP             172.17.0.2/16 fe80::42:acff:fe11:0002/64 mtu 1500

Sens : le conteneur croit que la MTU est 1500.
Décision : si le chemin contient de l’encapsulation (VPN/VXLAN), vous devrez probablement réduire la MTU dans le conteneur ou appliquer le MSS clamping.

Tâche 5 : Chercher des interfaces de tunnel et leur MTU

cr0x@server:~$ ip -br link | egrep 'wg0|tun0|tap|vxlan|geneve|gre' || true
wg0              UP             10.8.0.2/24 fe80::aaaa:bbbb:cccc:dddd/64 mtu 1420

Sens : WireGuard MTU 1420 est un indice fort. Si le trafic du conteneur sort via wg0, des paquets 1500 ne passeront pas.
Décision : soit réduire la MTU du conteneur/bridge pour correspondre à la plus petite MTU de sortie, soit clamer le MSS sur le trafic sortant via wg0.

Tâche 6 : Déterminer la MTU de chemin effective avec un ping DF (hôte)

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

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

Sens : l’hôte connaît déjà une MTU 1420 sur la route/interface pertinente (probablement parce que le trafic sort via wg0).
Décision : testez maintenant des tailles plus petites pour trouver le maximum qui passe ; confirmez la même chose depuis un conteneur.

Tâche 7 : Recherche binaire d’une taille DF qui fonctionne (hôte)

cr0x@server:~$ ping -c 2 -M do -s 1392 10.20.30.40
PING 10.20.30.40 (10.20.30.40) 1392(1420) bytes of data.
1400 bytes from 10.20.30.40: icmp_seq=1 ttl=62 time=12.3 ms
1400 bytes from 10.20.30.40: icmp_seq=2 ttl=62 time=12.1 ms

--- 10.20.30.40 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 12.051/12.173/12.296/0.122 ms

Sens : la MTU du chemin est autour de 1420 (1392 payload + 28 overhead ICMP/IP = 1420).
Décision : réglez MTU/MSS pour que les segments TCP payload rentrent sous ~1420 (pratiquement : MSS autour de 1380 en tenant compte des en-têtes/options).

Tâche 8 : Lancer un ping DF depuis un conteneur (pour détecter un décalage)

cr0x@server:~$ docker run --rm alpine:3.19 sh -lc 'ping -c 2 -M do -s 1472 10.20.30.40'
PING 10.20.30.40 (10.20.30.40): 1472 data bytes
ping: sendto: Message too long
ping: sendto: Message too long

--- 10.20.30.40 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss

Sens : le conteneur ne peut pas envoyer des paquets DF aussi grands ; soit il voit une MTU plus petite sur son egress, soit la pile refuse en raison d’une MTU découverte.
Décision : inspectez le routage depuis le namespace du conteneur et confirmez par quelle interface le trafic sort sur l’hôte.

Tâche 9 : Identifier le veth du conteneur et vérifier sa MTU sur l’hôte

cr0x@server:~$ cid=$(docker run -d alpine:3.19 sleep 300); echo "$cid"
b2e1e3d4c5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
cr0x@server:~$ pid=$(docker inspect -f '{{.State.Pid}}' "$cid"); echo "$pid"
24188
cr0x@server:~$ nsenter -t "$pid" -n ip -br link show dev eth0
eth0@if10        UP             172.17.0.2/16 fe80::42:acff:fe11:0002/64 mtu 1500
cr0x@server:~$ ifindex=$(nsenter -t "$pid" -n cat /sys/class/net/eth0/iflink); echo "$ifindex"
10
cr0x@server:~$ ip -br link | awk '$1 ~ /^veth/ {print}'
veth1a2b3c4d@if9 UP             mtu 1500

Sens : le veth est à 1500. Si le trafic sort via un tunnel à 1420, les paquets peuvent être trop gros à moins que la PMTUD fonctionne.
Décision : décidez si vous baissez la MTU sur bridge/veth ou si vous clampiez le MSS.

cr0x@server:~$ docker rm -f "$cid" >/dev/null

Tâche 10 : Vérifier si les ICMP « Fragmentation needed » sont filtrés

cr0x@server:~$ sudo iptables -S | egrep 'icmp|RELATED|ESTABLISHED'
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT

Sens : ICMP est autorisé en INPUT ici ; bon signe pour la PMTUD.
Décision : vérifiez aussi la chaîne FORWARD (Docker utilise le forwarding) et tout gestionnaire de pare-feu.

cr0x@server:~$ sudo iptables -S FORWARD
-P FORWARD DROP
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

Sens : FORWARD en DROP par défaut est acceptable si des exceptions existent, mais les erreurs ICMP sont « RELATED » et devraient passer. Si vous n’avez pas la règle RELATED,ESTABLISHED, la PMTUD casse.
Décision : assurez-vous que RELATED,ESTABLISHED est présent pour la direction pertinente, ou autorisez explicitement les types ICMP nécessaires.

Tâche 11 : Observer les retransmissions et le MSS avec tcpdump

cr0x@server:~$ sudo tcpdump -ni any 'host 10.20.30.40 and tcp' -c 10
tcpdump: data link type LINUX_SLL2
12:01:10.100001 IP 172.17.0.2.51234 > 10.20.30.40.8080: Flags [S], seq 1234567890, win 64240, options [mss 1460,sackOK,TS val 111 ecr 0,nop,wscale 7], length 0
12:01:10.102300 IP 10.20.30.40.8080 > 172.17.0.2.51234: Flags [S.], seq 2222222, ack 1234567891, win 65160, options [mss 1460,sackOK,TS val 222 ecr 111,nop,wscale 7], length 0
12:01:10.103000 IP 172.17.0.2.51234 > 10.20.30.40.8080: Flags [.], ack 1, win 502, options [nop,nop,TS val 112 ecr 222], length 0
12:01:11.105500 IP 172.17.0.2.51234 > 10.20.30.40.8080: Flags [P.], seq 1:1461, ack 1, win 502, options [nop,nop,TS val 113 ecr 222], length 1460
12:01:12.108000 IP 172.17.0.2.51234 > 10.20.30.40.8080: Flags [P.], seq 1:1461, ack 1, win 502, options [nop,nop,TS val 114 ecr 222], length 1460

Sens : MSS est 1460. Des segments de données de 1460 octets sont retransmis, ce qui suggère qu’ils ne passent pas. Si le MTU du chemin est ~1420, ces segments sont trop grands après encapsulation.
Décision : clampez le MSS vers le bas (par ex. à 1360–1380) ou réduisez la MTU du réseau de conteneurs afin que le MSS négocié soit plus petit.

Tâche 12 : Vérifier la MTU de route et le routage politique (commun avec les clients VPN)

cr0x@server:~$ ip route get 10.20.30.40
10.20.30.40 dev wg0 src 10.8.0.2 uid 1000
    cache

Sens : le trafic sort via wg0. C’est l’interface à MTU réduite.
Décision : appliquez le MSS clamping sur la sortie de wg0 (ou sur l’entrée) pour le trafic forwardé des conteneurs, ou réglez le MTU Docker pour s’adapter à wg0.

Tâche 13 : Inspecter la configuration MTU du daemon Docker (si présent)

cr0x@server:~$ cat /etc/docker/daemon.json
{
  "log-driver": "journald"
}

Sens : pas de substitution MTU configurée.
Décision : si vous avez besoin d’un correctif stable persistant, configurez explicitement le MTU du daemon Docker (ou configurez votre CNI/overlay MTU).

Tâche 14 : Vérifier les sysctls qui influencent le comportement PMTUD

cr0x@server:~$ sysctl net.ipv4.ip_no_pmtu_disc net.ipv4.tcp_mtu_probing
net.ipv4.ip_no_pmtu_disc = 0
net.ipv4.tcp_mtu_probing = 0

Sens : la PMTUD est activée (bon), mais le probing TCP MTU est désactivé (par défaut).
Décision : n’activez pas le probing globalement pour « réparer » le MTU sans comprendre l’impact ; préférez des MTU/MSS corrects.

Correctifs efficaces : alignement MTU et MSS clamping

Stratégie de correction A : Aligner le MTU entre underlay, overlays et interfaces de conteneurs

C’est la solution propre : les paquets ont naturellement la bonne taille, la PMTUD est un filet de sécurité, et vos captures sont ennuyeuses. Cela demande plus de réflexion au départ, mais c’est moins magique.

1) Si vous utilisez un tunnel, acceptez son overhead

Si la sortie passe par wg0 avec MTU 1420, vous pouvez régler le bridge Docker sur 1420 (ou légèrement moins pour tenir compte d’overheads supplémentaires, selon votre chemin). L’idée : garantir que la MTU de l’interface conteneur n’est pas supérieure à la plus petite MTU effective du chemin.

2) Définir le MTU du daemon Docker (réseaux bridge)

Configurez Docker pour créer des réseaux avec un MTU spécifique. Exemple :

cr0x@server:~$ sudo sh -lc 'cat > /etc/docker/daemon.json <<EOF
{
  "mtu": 1420,
  "log-driver": "journald"
}
EOF'
cr0x@server:~$ sudo systemctl restart docker

Sens : les nouvelles interfaces conteneur créées par Docker devraient utiliser MTU 1420.
Décision : recréez les conteneurs affectés (les veth existants peuvent garder l’ancien MTU). Planifiez un déploiement contrôlé.

3) Pour les réseaux définis par l’utilisateur, fixer explicitement la MTU

cr0x@server:~$ docker network create --driver bridge --opt com.docker.network.driver.mtu=1420 appnet
a1b2c3d4e5f6g7h8i9j0

Sens : ce réseau utilisera MTU 1420 pour son bridge/veth.
Décision : attachez les workloads qui traversent le tunnel à ce réseau ; gardez les réseaux « local-only » à 1500 si approprié.

4) Overlay networks : calculez la MTU, ne devinez pas

Pour des overlays VXLAN sur un underlay 1500, 1450 est un réglage courant. Si l’underlay est lui-même tunnellisé (VPN), votre overlay MTU devra être plus petite encore. Une pile VXLAN-over-WireGuard peut vite se retrouver à l’étroit.

L’approche correcte est empirique : mesurez la MTU effective entre nœuds (ping DF), puis soustrayez les overheads d’encapsulation que vous ajoutez. Ou, plus simplement : fixez l’overlay MTU en fonction du plus petit underlay réel que vous avez, pas celui du bon de commande.

Stratégie de correction B : Clamp MSS (rapide, pratique, un peu moche)

Le MSS clamping est le garrot du terrain : il arrête l’hémorragie même si vous n’avez pas réparé l’anatomie. Il marche particulièrement bien quand la MTU du chemin est stable mais plus petite que ce que croient les points d’extrémité, ou quand vous ne pouvez pas changer la MTU sur tous les conteneurs/réseaux rapidement.

Ce que ça fait : réécrit le MSS dans les paquets SYN pour que les points d’extrémité n’envoient jamais de segments TCP trop grands pour le chemin. Cela ne règle pas directement les protocoles basés sur UDP, et cela ne corrige pas IPv6 à moins d’appliquer l’équivalent dans ip6tables/nft pour v6.

1) Clamper le MSS sur le trafic conteneur forwardé sortant d’un tunnel

Si les conteneurs sont derrière un NAT sur l’hôte et sortent via wg0, clampez sur le chemin FORWARD.

cr0x@server:~$ sudo iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Sens : le kernel ajuste le MSS en fonction de la PMTU découverte pour cette route.
Décision : si la PMTUD est cassée (ICMP bloqué), le –clamp-mss-to-pmtu peut ne pas converger ; alors utilisez un MSS fixe.

2) Clamper sur un MSS fixe quand la PMTUD est peu fiable

cr0x@server:~$ sudo iptables -t mangle -A FORWARD -o wg0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360

Sens : le MSS devient 1360 pour ces flux, tenant à l’intérieur d’un MTU ~1420 avec marge pour en-têtes/options.
Décision : choisissez un MSS conservateur. Trop bas coûte en débit ; trop élevé ne résout pas le problème.

3) Vérifier que le MSS clamping est actif

cr0x@server:~$ sudo iptables -t mangle -S FORWARD | grep TCPMSS
-A FORWARD -o wg0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360

Sens : la règle est présente.
Décision : relancez tcpdump et confirmez que le SYN annonce MSS 1360.

4) Confirmer avec tcpdump que le MSS a changé

cr0x@server:~$ sudo tcpdump -ni wg0 'tcp[tcpflags] & tcp-syn != 0 and host 10.20.30.40' -c 2
12:05:44.000001 IP 10.8.0.2.53312 > 10.20.30.40.8080: Flags [S], seq 1, win 64240, options [mss 1360,sackOK,TS val 333 ecr 0,nop,wscale 7], length 0
12:05:44.002000 IP 10.20.30.40.8080 > 10.8.0.2.53312: Flags [S.], seq 2, ack 2, win 65160, options [mss 1360,sackOK,TS val 444 ecr 333,nop,wscale 7], length 0

Sens : le MSS est maintenant 1360 ; vous devriez cesser d’envoyer des segments surdimensionnés.
Décision : relancez le test du « large POST ». S’il réussit, vous avez votre mitigation immédiate.

Stratégie de correction C : Cesser de casser la PMTUD (l’équipe sécurité survivra)

Si vous contrôlez les pare-feux, autorisez l’ICMP nécessaire à la PMTUD :

  • IPv4 : ICMP type 3 code 4 (« Fragmentation needed »).
  • IPv6 : ICMPv6 type 2 (« Packet Too Big »).

Autorisez aussi le trafic conntrack « RELATED » pour les flux forwardés. Les hôtes Docker forwardent du trafic ; traitez-les comme des routeurs.

Joke #2 : bloquer l’ICMP pour « améliorer la sécurité » revient à enlever le voyant d’huile parce qu’il est distrayant.

Trois micro-histoires d’entreprise (toutes vraies dans l’esprit)

Incident 1 : La mauvaise hypothèse (« MTU est toujours 1500 »)

Une équipe a déployé un nouveau service d’ingestion conteneurisé. Il fonctionnait parfaitement en staging, puis a commencé à échouer en production seulement pour certains clients. Les petites charges étaient ok. Les gros uploads se bloquaient et aboutissaient à des timeouts après quelques minutes. Les logs applicatifs étaient inutiles : la requête arrivait, puis plus rien. Les métriques du load balancer montraient des connexions bloquées en état « active ».

Le on-call a d’abord suspecté une dépendance en amont lente. Raisonnable, car les « timeouts » en systèmes distribués sont souvent des douleurs de dépendances. Mais les captures paquet montraient des retransmissions de segments TCP pleine taille. SYN/SYN-ACK semblaient normaux. Le premier morceau du corps de la requête avait disparu dans le vide.

L’hypothèse était simple : « le MTU réseau est 1500. » Sur la NIC hôte, il l’était. Sur le chemin vers certains réseaux clients, non : le trafic était hairpiné via un tunnel IPsec géré par une autre équipe. Ce tunnel avait une MTU effective proche de 1400, et l’ICMP était filtré « pour la sécurité ».

La correction fut double : autoriser l’ICMP lié à la PMTUD à travers ce tunnel et clamer le MSS au bord du tunnel. Une fois appliqués, les gros uploads redevinrent ennuyeux. L’après-incident fut sévère mais productif : on ne peut pas traiter le « MTU » comme une propriété d’une seule interface. C’est une propriété du chemin, et les chemins changent.

Incident 2 : L’optimisation qui s’est retournée contre eux (« activer les jumbo frames partout »)

Une autre organisation voulait améliorer le débit pour les transferts conteneur-à-conteneur. Quelqu’un a proposé d’activer les jumbo frames (MTU 9000) sur le réseau de données. C’était présenté comme un gain faible risque : moins de paquets, moins de CPU, meilleur débit. Ils ont testé entre deux hôtes. C’était plus rapide. Tout le monde a applaudi et mergé la modification.

Une semaine plus tard, des choses étranges ont commencé. Certains services allaient bien. D’autres avaient des problèmes intermittents : streams gRPC reset, échecs occasionnels de handshake TLS, timeouts « aléatoires » lors de pull d’images. Le plus déroutant : les graphes de perte de paquets restaient propres. La latence paraissait normale. Seuls certains chemins étaient affectés.

Le problème n’était pas les jumbo frames en elles-mêmes. Le problème était un déploiement partiel et un unique saut en 1500 au milieu : un cluster de pare-feux qui ne supportait pas les jumbo sur une interface. Certains trafics passaient via un chemin jumbo-capable ; d’autres non, selon le routage et le basculement.

Le réseau avait maintenant un MTU en cerveau partagé. Les hôtes émettaient joyeusement des trames 9000. Quand elles heurtaient le saut limité à 1500, elles comptaient sur la PMTUD. L’ICMP « Packet Too Big » était limitant en taux sur le pare-feu et parfois droppé. Le résultat fut des trous noirs qui allaient et venaient avec la charge et les événements de basculement.

Ils standardisèrent finalement le MTU à 1500 sur ce segment et utilisèrent le tuning LRO/GRO plus du parallélisme applicatif pour la performance. Les jumbo frames peuvent être excellentes, mais le seul MTU jumbo sûr est celui supporté de bout en bout.

Incident 3 : La pratique ennuyeuse qui a sauvé la situation (tests MTU standard dans le rollout)

Une équipe plateforme maintenait un script de « readiness node » baseline pour chaque nouveau nœud de cluster. Ce n’était pas glamour. Il lançait quelques vérifications : DNS, synchronisation horaire, espace disque, et—discrètement—une paire de tests PMTU vers des destinations clés (control plane, registry, gateways du service mesh). Il validait aussi que les réseaux conteneurs avaient les MTU attendues.

Lors d’une migration, un sous-ensemble de nœuds fut provisionné dans un sous-réseau différent qui routait via un appliance VPN. Les workloads planifiés sur ces nœuds affichèrent immédiatement des taux d’erreur plus élevés, mais le script de readiness l’a détecté avant que la zone d’impact ne devienne problématique. Le test PMTU échoua pour des pings DF de 1500 et suggéra une MTU plus petite.

Parce que cela fut détecté tôt, la correction fut chirurgicale : l’équipe posa le MTU du réseau Docker sur ces nœuds pour correspondre au chemin VPN et ajouta le MSS clamping sur la sortie VPN. Ils documentèrent aussi la différence de sous-réseau pour que le réseau puisse ensuite retirer le tunnel inutile.

Rien de tout cela n’était héroïque. C’est le point. Des vérifications MTU routinières ont transformé une enquête de plusieurs jours en un changement de 30 minutes. L’ennuyeux est une fonctionnalité en opérations.

Erreurs courantes : symptôme → cause racine → correctif

1) Symptôme : « Les petites requêtes fonctionnent ; les gros POSTs bloquent depuis des conteneurs »

Cause racine : décalage MTU plus PMTUD cassée (ICMP bloqué) ou overhead de tunnel non pris en compte.
Correctif : mesurer la PMTU avec ping DF ; régler la MTU du réseau Docker en conséquence ou clamer le MSS sur l’interface de sortie.

2) Symptôme : « Ça marche sur l’hôte, échoue dans le conteneur »

Cause racine : le chemin réseau du conteneur diffère (règles NAT/forwarding, table de routage différente, policy routing, overlay).
Correctif : inspecter ip route get sur l’hôte pour la destination ; tcpdump sur docker0 et l’interface de sortie ; clamp MSS pour le trafic forwardé ou aligner les MTU.

3) Symptôme : « Handshake TLS échoue de façon intermittente ; curl se bloque parfois après CONNECT »

Cause racine : enregistrements de handshake TLS volumineux dépassent la MTU du chemin ; retransmissions ; ICMP bloqué ou limité en taux.
Correctif : clamp MSS ; autoriser ICMP PTB/frag-needed ; vérifier avec tcpdump que le MSS est abaissé.

4) Symptôme : « Le réseau overlay laisse tomber de gros paquets ; le ping nœud-à-nœud marche »

Cause racine : la MTU de l’overlay n’est pas réduite pour l’overhead d’encapsulation (VXLAN/Geneve).
Correctif : définir la MTU de l’overlay/CNI (souvent ~1450 pour VXLAN sur un underlay 1500), assurer une config cohérente sur tous les nœuds.

5) Symptôme : « IPv6 uniquement : certaines destinations inaccessibles, blocages étranges »

Cause racine : ICMPv6 Packet Too Big bloqué ; les routeurs IPv6 ne fragmentent pas, donc la PMTUD est obligatoire.
Correctif : autoriser ICMPv6 type 2 ; clamp MSS pour TCP IPv6 si nécessaire ; ne plus traiter ICMPv6 comme optionnel.

6) Symptôme : « Après changement de MTU de l’hôte, les anciens conteneurs cassent toujours »

Cause racine : les paires veth existantes conservent l’ancien MTU ; Docker ne refait pas toujours les réseaux live.
Correctif : recréer réseaux/conteneurs ; définir le MTU du daemon / des réseaux utilisateur pour que les nouvelles attachements soient corrects.

7) Symptôme : « Seul le trafic via le VPN casse ; le trafic interne est OK »

Cause racine : la MTU de l’interface VPN est plus petite que le LAN ; la MTU du conteneur/bridge est trop grande ; PMTUD cassée à travers le VPN.
Correctif : clamp MSS sur la sortie VPN ; optionnellement utiliser un réseau Docker dédié avec MTU réduite pour les workloads sortant via le VPN.

8) Symptôme : « Service basé sur UDP (DNS, QUIC, syslog) perd de gros messages »

Cause racine : le MSS clamping n’aide pas UDP ; la fragmentation peut être bloquée ; la charge UDP dépasse la MTU du chemin.
Correctif : réduire la taille des messages applicatifs ; utiliser TCP si le protocole le permet ; aligner les MTU et autoriser la fragmentation/ICMP si approprié.

Check-lists / plan étape par étape

Checklist A : Contention rapide (15–30 minutes)

  1. Reproduire l’échec avec une grosse requête depuis un conteneur.
  2. Lancer un ping DF depuis l’hôte vers la destination et trouver la taille maximale qui passe.
  3. Identifier l’interface de sortie pour cette destination (ip route get).
  4. Appliquer le MSS clamping sur l’interface de sortie pour le trafic forwardé :
    • Préférez --clamp-mss-to-pmtu si la PMTUD fonctionne.
    • Utilisez --set-mss si la PMTUD est cassée ou incohérente.
  5. Vérifier avec tcpdump que le SYN annonce un MSS réduit.
  6. Relancer le test de grosse requête. Confirmer le succès.

Checklist B : Correctif propre (même jour, moins d’adrénaline)

  1. Inventorier les couches d’encapsulation sur le chemin (VPN, overlay, cloud fabric, load balancers).
  2. Standardiser le MTU de l’underlay sur les liens devant être « le même réseau ».
  3. Fixer explicitement la MTU des overlays (VXLAN/Geneve/IPIP selon le cas) et garantir la cohérence sur tous les nœuds.
  4. Configurer le MTU du daemon Docker ou par-réseau pour les bridges qui doivent traverser des chemins contraints.
  5. Autoriser les ICMP requis pour la PMTUD (IPv4 frag-needed, IPv6 packet-too-big), y compris sur les chemins FORWARD.
  6. Documenter les valeurs MTU attendues et ajouter un test PMTU automatisé à la readiness node.

Checklist C : Validation après correction (ne la sautez pas)

  1. Capturer un court tcpdump pendant un transfert volumineux ; confirmer l’absence de retransmissions répétées de gros segments.
  2. Vérifier les taux d’erreur applicatifs ; confirmer que le symptôme spécifique disparait (pas seulement « ça a l’air mieux »).
  3. Vérifier les performances ; un MSS trop petit peut réduire le débit sur les chemins à grande BDP.
  4. Rebooter un nœud (ou redémarrer Docker) en fenêtre de maintenance pour s’assurer que la config persiste.

FAQ

1) Pourquoi les pings fonctionnent mais les uploads HTTP échouent ?

Les pings par défaut utilisent de petites charges qui tiennent sous presque tous les MTU. Les gros uploads HTTP génèrent de grands segments TCP qui dépassent la MTU du chemin et sont blackholés quand la PMTUD/ICMP est cassée.

2) Est-ce un bug Docker ?

Généralement non. Docker est un amplificateur : il ajoute des sauts supplémentaires (bridge, veth, NAT) et facilite l’envoi accidentel de trafic via des tunnels/overlays dont la MTU est plus petite que celle que croient les conteneurs.

3) Dois-je simplement régler le MTU à 1400 partout et passer à autre chose ?

Seulement si vous aimez payer une taxe permanente en performance et des régressions mystérieuses plus tard. Mesurez la plus petite MTU réelle dont vous avez besoin pour supporter, puis appliquez la MTU de manière appropriée par réseau. Utilisez le MSS clamping comme mitigation ciblée, pas comme mode de vie.

4) Qu’est-ce qui est mieux : baisser le MTU ou clamer le MSS ?

Baisser le MTU est plus propre et fonctionne pour TCP et UDP. Le MSS clamping est plus rapide à déployer et souvent suffisant pour TCP, mais il ne règle pas les problèmes de taille de charge UDP.

5) Pourquoi bloquer l’ICMP casse TCP ? TCP n’est pas ICMP.

La PMTUD utilise ICMP comme plan de contrôle pour signaler « votre paquet est trop grand ». Sans ce feedback, TCP continue d’envoyer des paquets DF surdimensionnés et retransmet indéfiniment. Le plan de données attend un signe qui ne vient jamais.

6) Kubernetes change-t-il quelque chose à cela ?

Les concepts sont identiques ; la surface d’attaque augmente. Les plugins CNI, l’encapsulation nœud-à-nœud et les service meshes ajoutent overhead et complexité. Kubernetes donne simplement plus d’endroits où se cacher à votre bug MTU.

7) Et IPv6 ?

IPv6 dépend encore plus de la PMTUD : les routeurs ne fragmentent pas. Si ICMPv6 Packet Too Big est bloqué, vous aurez des échecs francs sur les chemins avec MTU plus petite. Traitez ICMPv6 comme une infrastructure requise, pas comme du bruit optionnel.

8) Les sysctls de probing MTU TCP peuvent-ils régler ça ?

Parfois, mais c’est un dernier recours. Le probing MTU peut masquer des réseaux cassés en s’adaptant après perte, mais ce n’est pas un substitut à un MTU correct et à un ICMP fonctionnel. En production, préférez des correctifs déterministes.

9) Comment choisir une valeur MSS fixe ?

Partir de la MTU mesurée du chemin. Le MSS doit être MTU moins en-têtes. Pour TCP IPv4 sans options, c’est typiquement MTU-40, mais les options (timestamps, etc.) réduisent effectivement la marge. C’est pourquoi des valeurs comme 1360 pour un chemin 1420 sont courantes : conservatrices, sûres, pas maximales.

10) Pourquoi ça échoue seulement pour certaines destinations ?

Parce que le MTU est une propriété du chemin, et les routes diffèrent. Une destination peut rester sur votre LAN en 1500 ; une autre traverse un tunnel en 1420 ; une troisième traverse un pare-feu mal configuré qui droppe l’ICMP. Même code, physique différent.

Conclusion : prochaines étapes pratiques

Quand de grosses requêtes échouent depuis des conteneurs et que tout le reste semble « ok », suspectez MTU/MSS en priorité. Ce n’est pas de la superstition ; c’est un mode d’échec éprouvé avec des empreintes cohérentes : blocages dépendant de la taille, retransmissions, et un chemin incapable silencieusement de transporter ce que vous envoyez.

Faites ceci ensuite :

  1. Reproduisez avec une grosse charge depuis un conteneur et depuis l’hôte. Confirmez la forme du bug.
  2. Mesurez la PMTU avec des pings DF vers la destination réelle.
  3. Trouvez l’interface de sortie réelle et tout tunnel/overlay sur le chemin.
  4. Mitigez immédiatement avec le MSS clamping si vous avez besoin de disponibilité tout de suite.
  5. Corrigez proprement en alignant les MTU entre réseaux Docker, overlays et underlays, et en autorisant l’ICMP requis pour la PMTUD.
  6. Rendez cela ennuyeux pour toujours : ajoutez des tests PMTU à la readiness node et standardisez les MTU en tant que code.
← Précédent
Taux de cache ZFS : quand ils comptent et quand ils ne comptent pas
Suivant →
Debian 13 : rotation des clés SSH — révoquer proprement les accès et éviter la prolifération des clés (cas n°13)

Laisser un commentaire