
🌐 Aussi en: English · Deutsch · Español
Beaucoup d’utilisateurs de mes instances Nextcloud sont sur smartphone. C’est exactement pour cela qu’HTTP/3 a été conçu : il passe par QUIC au lieu de TCP, demande un aller-retour de moins pour établir une connexion, supporte mieux la perte de paquets et peut conserver une connexion quand le téléphone passe du Wi-Fi aux données mobiles. Mon objectif n’était pourtant pas « avoir HTTP/3 », mais : HTTP/3 seulement s’il apporte un gain mesurable – plus de débit ou une connexion plus stable sur un mauvais réseau.
Cet article décrit comment je l’ai activé, comment je prouve qu’il fonctionne vraiment – et les deux freins qui rendaient HTTP/3 nettement plus lent qu’HTTP/2 au départ.
🔌 Le contrôleur ne sait pas faire – son nginx, si
Mon ingress est le NGINX Ingress Controller de F5. Il n’a aucune option pour HTTP/3 : pas d’interrupteur, pas de clé de ConfigMap, rien dans les notes de version. Un coup d’œil dans le pod en cours d’exécution montre pourtant que le nginx 1.31.7 fourni est compilé avec --with-http_v3_module, avec OpenSSL 3.5, la première version d’OpenSSL qui gère QUIC côté serveur. Et comme le contrôleur tourne avec hostNetwork, nginx peut ouvrir directement l’UDP 443.
Cela passe donc par les snippets que j’utilise déjà :
# server-snippets of the main site (once per address):
listen 443 quic reuseport rcvbuf=4m sndbuf=4m;
listen [::]:443 quic reuseport rcvbuf=4m sndbuf=4m;
# http-snippets of the controller:
map $scheme $http3_alt_svc {
https 'h3=":443"; ma=604800';
default "";
}
http3_stream_buffer_size 1m;
quic_gso on;
# next to the other security headers (location-snippets):
add_header Alt-Svc $http3_alt_svc always;reuseportune seule fois par adresse : nginx n’accepte cette option que dans une seule lignelistenpar adresse et port. Elle se trouve donc uniquement dans le bloc serveur du site principal, et tous les autres hôtes partagent ces sockets.- Alt-Svc uniquement en HTTPS : cet en-tête indique aux navigateurs qu’HTTP/3 existe. La
mapne le renseigne qu’en HTTPS ; sur le port 80, la valeur reste vide et nginx n’envoie alors aucun en-tête. - Alt-Svc à côté des autres en-têtes : nginx n’hérite plus des
add_headerdès qu’une location définit ses propres en-têtes. Mes en-têtes de sécurité se trouvent dans les location-snippets – Alt-Svc doit donc y figurer aussi. - Pare-feu : UDP 443 en entrée pour IPv4 et IPv6. En sortie, aucune règle n’est nécessaire : les réponses sont « established » pour conntrack.
Tout dépend d’un interrupteur par hôte (ingress_http3_enabled), désactivé par défaut. J’ai d’abord activé le blog, puis la plus petite instance Nextcloud. Avant l’exécution réelle, j’ai vérifié les nouvelles lignes avec nginx -t sur une copie de la configuration dans le pod.
À retenir : « non pris en charge » signifie parfois seulement « pas d’interrupteur ». nginx -V montre ce que le binaire sait vraiment faire.
🤔 Pourquoi j’ai longtemps cru HTTP/3 impossible
J’avais déjà vérifié en août – avec pour résultat « impossible ». C’est aussi ce qu’indiquait depuis un commentaire dans mon modèle de pare-feu. À l’époque, j’avais vérifié deux choses : le contrôleur a-t-il une option pour HTTP/3 ? Non. Le serveur répond-il en HTTP/3 ? Non, évidemment – il n’y avait pas de listener QUIC.
Ce que je n’avais pas vérifié : si le nginx du contrôleur contient le module. La conclusion « pas d’option, donc pas de prise en charge » était trop rapide. On ne peut plus savoir aujourd’hui si l’image de l’époque avait déjà le module – l’actuelle l’a, avec une version d’OpenSSL qui gère QUIC côté serveur. C’est la question « que sait faire le binaire ? » au lieu de « qu’offre la configuration ? » qui a mené à l’autre réponse.
À retenir : un « impossible » mérite une date et une justification – et une seconde vérification quand le logiciel a changé.
✅ Comment voir qu’HTTP/3 fonctionne vraiment
Une page qui s’affiche ne dit rien du protocole. J’ai fait ces vérifications après chaque activation :
curl -sI --http2 https://www.apt-upgrade.me/ | grep -i alt-svc
alt-svc: h3=":443"; ma=604800
curl -s --http3-only -o /dev/null -w '%{http_code} h%{http_version}\n' https://www.apt-upgrade.me/
200 h3- HTTP/3 forcé avec
curl --http3-onlysur tous les chemins importants : page d’accueil, connexion,status.phpde Nextcloud, l’API OCS, WebDAV, Collabora – partout la réponse attendue en h3. - Même contenu : un gros fichier JavaScript chargé en HTTP/2 et en HTTP/3 – somme SHA-256 identique. Les en-têtes de réponse sont également les mêmes.
- Côté serveur :
ss -ulnpmontre un socket UDP par worker nginx sur le port 443, en IPv4 et IPv6 ; le journal d’accès contient des requêtes enHTTP/3.0. - Depuis l’extérieur : http3check.net confirme QUIC version 1 et HTTP/3, testé en IPv6 – ce que je ne peux pas du tout tester depuis chez moi.
- Sécurité inchangée : les filtres fail2ban reconnaissent les lignes de connexion en
HTTP/3.0comme celles enHTTP/2.0. Les données précoces 0-RTT restent désactivées, pour que personne ne puisse rejouer un formulaire de connexion.
Un petit piège : mon premier test fail2ban a échoué – avec une ligne de journal fabriquée à la main, au mauvais format. Avec une vraie ligne du journal, où seul le protocole avait été remplacé, le filtre a correspondu aussitôt.
À retenir : une preuve demande plusieurs points de vue – le client, le serveur, un tiers à l’extérieur et ses propres outils de sécurité.
🐢 Frein n° 1 : 64 Ko par aller-retour
Les petites requêtes étaient aussi rapides, voire plus rapides, en HTTP/3 dès le départ. Puis est venu le test avec un gros fichier : 5,7 Mo prenaient 0,18 seconde en HTTP/2 et 0,99 seconde en HTTP/3 – plus de cinq fois plus longtemps.
Le client n’utilisait presque pas de CPU ; il attendait. Le calcul a mené à la cause : par défaut, nginx ne met en tampon que 64 Ko par flux QUIC (http3_stream_buffer_size). Un flux ne peut pas avoir plus de données en transit. 64 Ko par aller-retour à 11 ms donnent exactement les 5,8 Mo/s mesurés. Sur une liaison mobile à 50 ms, ce serait environ 1,3 Mo/s – HTTP/3 aurait donc ralenti mes utilisateurs en déplacement.
Avec un tampon de 1 Mo, les téléchargements en HTTP/3 atteignaient 27 à 31 Mo/s, à égalité avec HTTP/2.
À retenir : un nouveau protocole apporte de nouvelles valeurs par défaut. Un seul test avec un gros fichier a suffi à trouver le frein – c’est pourquoi il fait partie de toute recette.
🧺 Frein n° 2 : un tampon de socket qui déborde
Puis l’autre sens : un envoi de 20 Mo. HTTP/3 atteignait en moyenne 7,8 Mo/s, HTTP/2 11,9 Mo/s – et pendant ce temps, le serveur a compté 284 paquets UDP rejetés (RcvbufErrors dans /proc/net/snmp).
La raison tient à la différence avec TCP. Avec TCP, le noyau gère lui-même le flux et les tampons. QUIC, en revanche, tourne dans l’application, au-dessus d’UDP : chaque paquet attend dans le tampon de réception du socket jusqu’à ce que nginx le récupère. Par défaut, Linux accorde au maximum 208 Ko à un socket – un envoi rapide le remplit, et le noyau jette les paquets suivants. QUIC doit les renvoyer et se freine lui-même.
La solution comporte deux parties :
- Relever la limite du noyau :
net.core.rmem_maxetnet.core.wmem_maxà 7,5 Mo – la valeur recommandée par les implémentations de QUIC. Cela ne relève que le plafond ; tous les autres sockets gardent leur valeur par défaut. - Demander les tampons : les listeners QUIC réclament eux-mêmes des tampons plus grands avec
rcvbuf=4m sndbuf=4m. Le noyau double la valeur en interne ;ss -maffiche ensuite 8 Mo par socket.
Ensuite : 0 paquet rejeté, et les envois en HTTP/3 se situaient au niveau d’HTTP/2 – les variations restantes venaient de ma propre connexion.
À retenir : qui fait passer de l’UDP à haut débit doit connaître ses tampons de socket. Le noyau compte les paquets rejetés – il suffit de regarder.
📊 Les mesures
J’ai mesuré depuis mon homelab avec curl, 15 nouvelles connexions par valeur (médiane). Le blog est plus éloigné (environ 42 ms par aller-retour), le Nextcloud plus proche (environ 11 ms).
| Mesure | HTTP/2 | HTTP/3 |
|---|---|---|
| Blog : connexion + chiffrement prêts | 96 ms | 58 ms |
| Blog : premier octet de la page d’accueil | 139 ms | 104 ms |
| Blog : page d’accueil complète | 268 ms | 227 ms |
| Nextcloud : connexion + chiffrement prêts | 31 ms | 27 ms |
| Nextcloud : téléchargement de 5,7 Mo, tampon par défaut | 34 Mo/s | 5,8 Mo/s |
| Nextcloud : téléchargement de 5,7 Mo, tampon de flux de 1 Mo | 28 Mo/s | 30 Mo/s |
| Nextcloud : envoi de 20 Mo, tampon de socket de 208 Ko | 11,9 Mo/s | 7,8 Mo/s |
| Nextcloud : envoi de 20 Mo, tampon de socket de 8 Mo | 15,1 Mo/s | 13,0 Mo/s |
| Nextcloud : envoi de 100 Mo, tampon de socket de 8 Mo | 8,7 Mo/s | 7,7 Mo/s |
| Collabora : requête discovery (47 Ko) | 55–72 ms | 57 ms |
À 100 Mo, ma propre connexion montante limite les deux protocoles de la même façon, sans paquet rejeté. Des téléchargements plus gros n’ont pas pu être testés sans compte utilisateur : le plus gros fichier public du Nextcloud est le script de 5,7 Mo. Collabora édite les documents via des WebSockets, qui restent sur TCP – seule la requête discovery est mesurable.
Le gain à l’établissement de la connexion est d’un aller-retour : TCP et TLS en demandent deux, QUIC fait les deux en un. Pour le blog à 42 ms, cela représente environ 40 ms par nouvelle connexion ; pour le Nextcloud proche, presque rien. Sur les réseaux mobiles à 50–100 ms, le gain est d’autant plus grand.
À retenir : HTTP/3 fait surtout gagner du temps d’attente, pas de la bande passante. Plus l’utilisateur est loin, plus il aide.
🔧 Les autres réglages
quic_gso on: nginx transmet au noyau plusieurs paquets QUIC à la fois au lieu d’un par appel. Cela économise du CPU à l’envoi ; mes cartes réseau virtuelles le font en logiciel, ce qui aide quand même. Activé.- Alt-Svc pendant 7 jours au lieu d’un : les visiteurs réguliers démarrent plus longtemps directement en HTTP/3. Si l’on désactive HTTP/3 plus tard, chaque navigateur perd au plus une tentative – il revient de lui-même à HTTP/2. Réglé.
quic_bpf: garantit qu’après un changement de réseau, les paquets d’une connexion arrivent au bon worker nginx. Pour cela, nginx doit charger un programme eBPF, ce qui exige des droits supplémentaires (CAP_BPF,CAP_NET_ADMIN) – justement dans le conteneur exposé à Internet, qui n’a volontairement qu’un seul droit. Non activé : si une migration échoue, le téléphone se reconnecte, avec QUIC en un aller-retour.- 0-RTT (
ssl_early_data) : ferait gagner un aller-retour de plus aux connexions récurrentes, mais ces requêtes peuvent être rejouées – connexions comprises. Reste désactivé.
À retenir : chaque réglage a un prix. On active ce qui coûte peu ; ce qui affaiblit le durcissement reste désactivé, même si c’est plus rapide sur le papier.
🧭 Dès la première visite : l’enregistrement HTTPS dans le DNS
Alt-Svc a un défaut : un navigateur n’apprend l’existence d’HTTP/3 que par la première réponse. La toute première visite passe donc toujours par HTTP/2. Un enregistrement DNS dédié de type HTTPS (RFC 9460) y remédie : il indique au navigateur, dès la résolution du nom, quels protocoles le serveur parle.
# DNS zone (example): HTTPS record, priority 1, "." = same name
www.example.org. 3600 IN HTTPS 1 . alpn="h3,h2"
# check it:
dig +short HTTPS www.example.org
1 . alpn="h3,h2"Le 1 est la priorité, le point signifie « le même nom », et alpn liste les protocoles dans l’ordre souhaité. Je renonce volontairement aux indications d’adresses IP – il faudrait les mettre à jour à chaque changement d’adresse. Chrome, Firefox et Safari exploitent cet enregistrement. Toutes les interfaces de gestion DNS ne proposent pas ce type ; mieux vaut vérifier avant de chercher pourquoi il manque.
C’est précisément mon cas : la gestion DNS de Strato, où se trouve apt-upgrade.me, ne propose que les enregistrements A, AAAA, CNAME, MX, NS, TXT et SRV – je ne peux pas y créer d’enregistrement HTTPS. On ne peut pas non plus l’imiter : les navigateurs n’exploitent que le vrai type. Le blog reste donc sur Alt-Svc : la première visite passe par HTTP/2, chaque visite suivante directement par HTTP/3 pendant sept jours. Changer de fournisseur DNS uniquement pour cela n’en vaudrait pas la peine à mes yeux.
À retenir : le serveur ne peut que proposer HTTP/3. C’est le DNS qui décide si le navigateur l’utilise dès la première visite.
📱 Ce que cela apporte à mes utilisateurs – et ce que cela n’apporte pas
- Navigateurs sur smartphone : le plus grand gain – établissement plus rapide, pas de blocage de toutes les requêtes quand un paquet se perd, et la connexion survit au passage entre Wi-Fi et données mobiles.
- Application Nextcloud sur iPhone : iOS utilise HTTP/3 de lui-même dès que le serveur l’annonce via Alt-Svc.
- Application Android et client de bureau : à ma connaissance, ils utilisent des bibliothèques réseau sans HTTP/3. Le journal d’accès montrera la part réelle au bout de quelques jours.
- Collabora et notify_push : passent par des WebSockets. nginx ne gère pas les WebSockets sur HTTP/3, ces connexions restent donc en HTTP/1.1 sur TCP – cela fonctionne, simplement sans le gain.
Ce que je n’ai pas pu mesurer d’ici, c’est le comportement sur un mauvais réseau. Ce sera le prochain test : avec un smartphone dans une zone à faible réception, HTTP/3 activé et désactivé en comparaison.
À retenir : rester honnête sur qui en profite. Une mise à niveau de protocole n’aide que les clients qui le parlent.
📅 État actuel et suite
HTTP/3 tourne désormais sur les trois serveurs : le blog et les deux instances Nextcloud. J’ai validé la seconde instance Nextcloud avec les mêmes contrôles que la première, et la vérification de dérive affiche changed=0 partout – les serveurs correspondent exactement au code du dépôt public.
Dans une semaine, je regarderai trois choses :
- Utilisation : quelle part des requêtes du journal d’accès arrive en
HTTP/3.0– séparément pour les navigateurs, les applications Nextcloud et les clients de synchronisation ? - Erreurs : y a-t-il plus de réponses d’erreur ou d’envois interrompus en HTTP/3 qu’en HTTP/2 ?
- Pratique : comment se comporte le Nextcloud sur smartphone avec une faible réception, avec et sans HTTP/3 ?
Je publierai les chiffres dans les sept prochains jours, dans un article à part. Si HTTP/3 n’apporte presque rien à mes utilisateurs ou provoque plus d’erreurs, je le désactive serveur par serveur – un interrupteur et une exécution de playbook.
🎯 Les leçons
- Vérifier d’abord ce que sait faire le binaire – « pas d’option » ne veut pas dire « pas de module ».
- Tester avec de gros fichiers, dans les deux sens – les petites requêtes cachent tous les freins au débit.
- Les tampons UDP font partie de QUIC – les
RcvbufErrorsmontrent si le noyau jette des paquets. - Tester avec de vraies lignes de journal – un exemple fabriqué ne teste que l’exemple.
- L’activer parce qu’il aide – mesuré, pas supposé, et avec un interrupteur qu’on peut remettre en arrière.





