Le serveur est sur une liaison Gbit. L'interface réseau affiche 1000 Mbps dans
ethtool. Et pourtant, chaque transfert SFTP plafonne inexorablement
aux alentours de 800 KB/s. Pas 80 MB/s — 800 kilooctets par seconde.
Soit moins de 1% de la capacité théorique.
La cause n'est pas un problème de bande passante. C'est une accumulation de mauvais défauts : un algorithme de congestion conçu pour des réseaux des années 2000, des ring buffers NIC microscopiques, et des socket buffers applicatifs configurés pour des lignes ADSL. Voici les cinq réglages qui ont changé la situation.
TCP BBR : remplacer CUBIC
CUBIC est l'algorithme de contrôle de congestion par défaut sur Linux depuis 2006. Il fonctionne en augmentant la fenêtre de congestion (cwnd) jusqu'à détecter une perte de paquet, puis en la réduisant brutalement. Sur les réseaux modernes à faible latence et haut débit, cette réaction aux pertes pose un problème : un seul paquet perdu (bruit thermique, buffer overflow NIC) provoque une chute de débit de 50%, suivie d'une remontée lente.
BBR (Bottleneck Bandwidth and Round-trip propagation time), développé par Google en 2016, ne réagit pas aux pertes — il modélise le débit réel du lien en mesurant la bande passante et la latence minimale en continu. Il maintient la fenêtre au niveau optimal sans attendre les drops. Sur un lien Gbit avec du vrai trafic, la différence est immédiatement perceptible.
# /etc/sysctl.d/99-bbr.conf
net.ipv4.tcp_congestion_control = bbr # BBR remplace CUBIC
net.ipv4.tcp_slow_start_after_idle = 0 # maintient cwnd entre les pauses de téléchargement
net.ipv4.tcp_no_metrics_save = 1 # ignore les métriques TCP des sessions précédentes
net.ipv4.tcp_fastopen = 3 # TCP Fast Open client+serveur (-1 RTT sur reconnexions)
net.ipv4.tcp_mtu_probing = 1 # MTU probing si ICMP bloqué par un pare-feu upstream
net.ipv4.tcp_wmem = 4096 262144 16777216 # buffer écriture TCP : default 256 KB, max 16 MB
net.core.netdev_max_backlog = 5000 # queue paquets avant traitement kernel à 1 Gbit/s
net.core.netdev_budget = 600 # paquets traités par cycle NAPI
# Appliquer immédiatement (persistant au reboot via le fichier)
sysctl -p /etc/sysctl.d/99-bbr.conf
# Vérifier
sysctl net.ipv4.tcp_congestion_control
# → net.ipv4.tcp_congestion_control = bbr
tcp_slow_start_after_idle mérite une attention particulière pour SFTP.
Par défaut, Linux réinitialise la cwnd à 10 après une période d'inactivité sur une
connexion TCP. Or une session SFTP interactive alterne activité et pauses —
chaque fois qu'on ouvre un nouveau transfert après avoir navigué dans le répertoire,
TCP repart en slow start. Désactiver ce comportement évite ce reset inutile.
Vérifier que le module BBR est bien chargé :
lsmod | grep bbr
# → tcp_bbr 20480 1
# Si absent : modprobe tcp_bbr
Ring buffers NIC : la cause principale des drops
C'est probablement le réglage le plus impactant, et le moins documenté. Un ring buffer NIC est une zone mémoire entre la carte réseau et le kernel. La NIC y dépose les paquets reçus. Si le buffer est plein avant que le kernel ne les traite, les nouveaux paquets sont silencieusement abandonnés.
La valeur par défaut sur la plupart des cartes Ethernet est 256 ou 512 entrées en RX. À 1 Gbit/s avec des trames de 1500 octets, on parle de ~83 000 paquets par seconde à absorber. Un buffer de 256 entrées peut saturer en moins d'une milliseconde si le kernel est temporairement occupé ailleurs. Résultat : des drops invisibles, une cwnd CUBIC qui s'effondre, un débit qui stagne.
# Voir l'état actuel
ethtool -g enp2s0f0
# Ring parameters for enp2s0f0:
# Pre-set maximums:
# RX: 4096
# TX: 4096
# Current hardware settings:
# RX: 256 ← trop petit
# TX: 256 ← trop petit
# Appliquer (prend effet immédiatement, perdu au reboot)
ethtool -G enp2s0f0 rx 4096 tx 4096
# Vérifier
ethtool -g enp2s0f0
# Current hardware settings:
# RX: 4096
# TX: 4096
Pour rendre le réglage persistant, l'ajouter dans /etc/network/interfaces
après la configuration de l'interface :
# Dans /etc/network/interfaces, après la ligne dns-search (ou en bas du bloc iface)
post-up ethtool -G enp2s0f0 rx 4096 tx 4096
Remplacer enp2s0f0 par le nom réel de l'interface
(ip link show pour lister). Toutes les NIC ne supportent pas 4096 —
ethtool -g indique les maximums dans "Pre-set maximums".
Pour observer les drops NIC en temps réel :
watch -n1 ethtool -S enp2s0f0 | grep -i drop
# rx_missed_errors et rx_fifo_errors indiquent les drops ring buffer
ProFTPD et Apache : les buffers applicatifs
Une fois les couches kernel et NIC optimisées, les buffers applicatifs deviennent le goulot suivant. Les valeurs par défaut de ProFTPD et Apache sont configurées pour des lignes modestes — elles n'ont pas changé depuis une époque où un Gbit/s était réservé aux backbones des opérateurs.
ProFTPD — socket buffers
ProFTPD expose SocketOptions pour contrôler la taille des buffers
de socket au niveau TCP. Les valeurs par défaut (rcvbuf et sndbuf)
sont typiquement de 87 KB ou moins. Passer à 1 MB permet au kernel de tenir
davantage de données en transit sans avoir à attendre les ACKs du client :
# Dans /etc/proftpd/proftpd.conf, après la ligne UseSendFile on
SocketOptions rcvbuf 1048576 sndbuf 1048576
systemctl reload proftpd
Apache — send buffer
Apache a son propre réglage SendBufferSize qui contrôle la taille
du buffer d'envoi TCP pour les connexions HTTP. Sans ce paramètre, Apache utilise
la valeur système par défaut, souvent 87 KB. Sur une liaison Gbit qui sert
des fichiers statiques volumineux ou des réponses API conséquentes, c'est limitant.
# Dans /etc/apache2/apache2.conf (au niveau global)
SendBufferSize 1048576
apache2ctl configtest && systemctl reload apache2
TLS : session cache et OCSP Stapling
Chaque handshake TLS complet coûte 1 à 2 RTT supplémentaires. Sur HTTPS, un client qui se reconnecte fréquemment — navigateur qui réouvre des connexions, outil de transfert avec reconnexions, monitoring — multiplie ces RTT inutilement. La session cache TLS permet de reprendre une session existante sans refaire le handshake complet.
OCSP Stapling résout un autre problème : sans stapling, le navigateur doit contacter le serveur OCSP de l'autorité de certification pour vérifier la validité du certificat. C'est un round-trip externe supplémentaire, potentiellement lent, sur chaque nouvelle connexion. Avec le stapling, Apache précharge la réponse OCSP et l'inclut dans le handshake TLS — le client n'a pas besoin de contacter l'AC.
# Dans /etc/apache2/mods-enabled/ssl.conf
# Session cache partagé en mémoire : 10 MB (~40 000 sessions simultanées)
SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(10485760)
SSLSessionCacheTimeout 3600
# OCSP Stapling : Apache précharge et met en cache la réponse OCSP
SSLUseStapling on
SSLStaplingCache shmcb:/var/run/apache2/ssl_stapling(2097152)
SSLStaplingReturnResponderErrors off
apache2ctl configtest && systemctl reload apache2
SSLStaplingReturnResponderErrors off est important : sans lui,
si le serveur OCSP upstream est temporairement injoignable, Apache renvoie
l'erreur au client plutôt que de continuer sans stapling. Ce n'est pas le comportement
voulu en production.
Résultat et vérifications
Après application de l'ensemble de ces réglages, le débit SFTP est passé de ~800 KB/s à plusieurs dizaines de MB/s sur la même connexion Gbit. Le facteur limitant n'était pas le réseau — c'était l'accumulation de ces mauvais défauts à chaque couche de la pile.
Commandes de vérification post-déploiement :
# BBR actif
sysctl net.ipv4.tcp_congestion_control
# → bbr
# Ring buffers NIC
ethtool -g enp2s0f0 | grep -A5 "Current hardware"
# → RX: 4096 / TX: 4096
# Statistiques TCP kernel (retransmissions, drops)
ss -s
# → retrans: 0/0 idéalement
# Débit SFTP de test (fichier de 1 GB)
sftp user@serveur <<< $'put /tmp/testfile /tmp/testfile'
# Mesurer avec time ou observer le débit affiché
# Vérifier OCSP Stapling
openssl s_client -connect votre-domaine.com:443 -status < /dev/null 2>&1 | grep -i "OCSP"
# → OCSP Response Status: successful (0x0)
# Vérifier session TLS (handshake résumé)
openssl s_client -connect votre-domaine.com:443 -reconnect 2>&1 | grep "Reused"
# → Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Conclusion
Ces réglages auraient pu être les défauts du kernel et des daemons depuis longtemps. BBR est stable depuis 2016. Un ring buffer de 256 entrées sur une carte Gbit n'a aucun sens. Un socket buffer de 87 KB sur une connexion moderne, c'est du folklore.
La difficulté n'est pas technique — chaque modification tient en une ligne. Le problème c'est que ces défauts ne génèrent pas d'erreur visible : le débit est juste "décevant", les logs ne montrent rien, et on passe des heures à chercher côté application avant de regarder la pile réseau.
À noter : adapter les valeurs à la topologie réelle.
tcp_wmem à 16 MB sur une machine avec 512 MB de RAM et 1000 connexions
simultanées peut poser d'autres problèmes. Ces paramètres sont calibrés pour
un serveur dédié avec peu de connexions simultanées et un réseau local ou datacenter.