Table des matières de l'article :
Au cours des derniers mois, le protocole HTTP / 3, basé sur QUIC, entre progressivement en production. Les principaux navigateurs le prennent en charge de manière stable, et de nombreux fournisseurs, dont Cloudflare, Google et Akamai, le considèrent désormais comme la norme de facto pour les connexions modernes. Nginx, à partir de la série 1.25, a introduit un module HTTP/3 expérimental, rendu plus stable plus tard avec la version 1.29.x.
Cependant, au cours de certaines sessions de tests approfondis menées par Serveur géré Srl, nous avons identifié un comportement anormal qui peut générer incohérences dans les réécritures, les journaux et les variables d'environnement, en particulier pour les sites et applications qui s'appuient sur la variable $http_host.
Le problème provenait d'une erreur de valorisation de cette variable lors du traitement des requêtes HTTP/3, c'est-à-dire des connexions QUIC. Contrairement à HTTP/1.1 et HTTP/2, où l'en-tête Host est automatiquement interprété et associé, dans le cas de HTTP/3 NGINX n'a pas effectué l'initialisation correcte de la valeur correspondante lorsque le client a envoyé uniquement le pseudo-en-tête :authority.
Le résultat? $http_host est resté vide ou incompatible avec la valeur de l'autorité demandée, compromettant la compatibilité avec de nombreuses configurations et générant un comportement inattendu dans plusieurs scénarios d'application.
La variable $http_host et son importance
Pour bien comprendre l’ampleur du problème, il convient de rappeler que $http_host C'est l'une des variables les plus utilisées dans l'environnement NGINX.
Il est souvent utilisé dans :
- blocs
serverelocationavec une logique conditionnelle; - réécritures dynamiques basées sur l'hôte ;
- règles de redirection vers des domaines canoniques ;
- journalisation personnalisée;
- proxy passe qui dépend de l'hôte de la requête d'origine.
Dans les configurations multi-domaines ou multi-locataires, l'absence ou l'incohérence de la valeur de $http_host Cela peut modifier le comportement de l'ensemble de la pile, obligeant un site à répondre avec le mauvais domaine ou empêchant les redirections HTTPS de fonctionner correctement.
La cause : le comportement de NGINX avec HTTP/3
Selon la RFC 9114, Section 4.2 (« Champs de pseudo-en-tête de demande »), chaque requête HTTP/3 doit contenir un champ :authority ou un en-tête Host.
Les deux ne peuvent pas être manquants et, s'ils sont présents, ils doivent contenir la même valeur.
Cependant, la plupart des clients HTTP/3 (tels que Chrome, Firefox et cURL dans --http3) envoyer uniquement le champ :authority, sans le dupliquer comme Host.

NGINX, jusqu'à la version 1.29.1, ne transmettait pas automatiquement cette valeur dans l'en-tête interne correspondant Host, à partir de laquelle la variable est ensuite dérivée $http_host.
Cela a provoqué un écart fonctionnel entre HTTP/1.1 / HTTP/2 et HTTP/3 : dans les deux premiers cas $http_host il était toujours disponible, dans le troisième il ne l'était pas.
Le comportement, bien que conforme au cahier des charges, ce n'était pas cohérent avec la philosophie opérationnelle de NGINX, où les variables d'environnement doivent maintenir l'uniformité entre les protocoles, afin de ne pas forcer l'administrateur à différencier les configurations entre HTTP/2 et HTTP/3.
Le Patch : une intégration simple mais essentielle
Après une analyse minutieuse du code source de NGINX, et en s'inspirant d'un correctif similaire déjà présent dans ANGIE, le fork développé par Web Server LLC, nous avons créé un patch minimal mais efficace.
L'amendement, proposé par Marco Marcoaldi (CTO de Managed Server Srl), consiste en l'ajout d'une fonction dédiée :
ngx_http_v3_set_host(ngx_http_request_t *r, ngx_str_t *valeur)
Cette fonction s'occupe d'initialiser le champ r->headers_in.host lorsque le champ n'est pas présent mais existe :authority, en copiant sa valeur en toute sécurité.
Le code, intégré au fichier source src/http/v3/ngx_http_v3_request.c, utilise la structure de données interne de NGINX pour allouer un nouvel en-tête « hôte » cohérent avec les requêtes HTTP/3 entrantes.
diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c
index 5a6d4f9..6b8d77e 100644
--- a/src/http/v3/ngx_http_v3_request.c
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -32,6 +33,8 @@
static ngx_int_t ngx_http_v3_process_request(ngx_http_request_t *r);
static ngx_int_t ngx_http_v3_parse_request_line(ngx_http_request_t *r);
+
+static ngx_int_t ngx_http_v3_set_host(ngx_http_request_t *r, ngx_str_t *value); // Enable $http_host
static ngx_int_t ngx_http_v3_parse_request_headers(ngx_http_request_t *r);
static ngx_int_t ngx_http_v3_parse_request_header(ngx_http_request_t *r);
@@ -1001,6 +1004,34 @@ ngx_http_v3_process_request(...)
+
+ngx_http_v3_set_host(ngx_http_request_t *r, ngx_str_t *value)
+{
+ ngx_table_elt_t *h;
+
+ static ngx_str_t host = ngx_string("host");
+
+ h = ngx_list_push(&r->headers_in.headers);
+ if (h == NULL) {
+ return NGX_ERROR;
+ }
+
+ h->hash = ngx_hash(ngx_hash(ngx_hash('h', 'o'), 's'), 't');
+
+ h->key.len = host.len;
+ h->key.data = host.data;
+
+ h->value.len = value->len;
+ h->value.data = value->data;
+
+ h->lowcase_key = host.data;
+
+ r->headers_in.host = h;
+ h->next = NULL;
+
+ return NGX_OK;
+}
+
+static ngx_int_t
ngx_http_v3_parse_request_header(ngx_http_request_t *r)
{
...
@@ -1038,7 +1068,8 @@ ngx_http_v3_parse_request_header(ngx_http_request_t *r)
- if (r->headers_in.host && r->host_end) {
+ if (r->host_end) {
+ /* full :authority value (possibly with port) */
ngx_str_t host;
host.len = r->host_end - r->host_start;
host.data = r->host_start;
@@ -1043,8 +1075,17 @@ ngx_http_v3_parse_request_header(ngx_http_request_t *r)
- if (r->headers_in.host->value.len != host.len
- || ngx_memcmp(r->headers_in.host->value.data, host.data, host.len)
- != 0)
- {
- ngx_log_error(NGX_LOG_INFO, c->log, 0,
- "client sent \":authority\" and \"Host\" headers "
- "with different values");
- goto failed;
- }
+ if (r->headers_in.host) {
+ /* both Host and :authority present - ensure they are equal */
+ if (r->headers_in.host->value.len != host.len
+ || ngx_memcmp(r->headers_in.host->value.data,
+ host.data, host.len) != 0)
+ {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent \":authority\" and \"Host\" headers "
+ "with different values");
+ goto failed;
+ }
+ } else {
+ /* Host is missing - set from :authority */
+ if (ngx_http_v3_set_host(r, &host) != NGX_OK) {
+ ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_ERROR;
+ }
+ }
@@ -1491,7 +1532,6 @@ ngx_http_v3_finalize_request(ngx_http_request_t *r)
...
-
@@ -1730,6 +1772,7 @@ ngx_http_v3_run_request(ngx_http_request_t *r)
...
+
Le patch inclut également une vérification logique supplémentaire : si les deux champs Host e :authority sont présents et contiennent des valeurs différentes, un avertissement est enregistré dans le journal (NGX_LOG_INFO) et la demande est rejetée, comme l'exigent les spécifications RFC.
En pratique, le équivalence sémantique complète tra Host e :authority, éliminant toute ambiguïté possible.
Tests et validation en environnement réel
Une fois la modification terminée, le patch a été testé en trois phases :
- Environnement de développement isolé, pour vérifier la compilation et le comportement du formulaire
http_v3. - Environnement de mise en scène, avec des simulations de trafic HTTP/3 et des outils comme
curl --http3,h2load,wrkeautocannon. - Environnement de production sur un site Magento 2, en utilisant le build
nginx-1.29.1avec le drapeau--with-http_v3_module.
Dans tous les tests, la variable $http_host il a été correctement évalué avec la valeur dérivée du champ :authority, même en l'absence d'en-têtes Host.
Aucune régression ou impact sur les performances n'a été observé, même sous charge soutenue ou avec des connexions simultanées sur QUIC.
Les réponses HTTP ont maintenu la latence et le débit inchangés par rapport à la version d'origine.
Fusionner la proposition dans la branche principale officielle
Une fois le comportement consolidé et la compatibilité vérifiée, nous avons décidé de rendre le changement public.
Il a ensuite été exécuté fork du dépôt officiel NGINX sur GitHub et ouvert le Demande de tirage n° 917, visible ici :
Le PR est accompagné d'une description technique, de références à la RFC 9114, d'un code de diff complet et de notes sur les tests de validation de production.
Au même moment, l'accord a été signé Contrat de licence de contributeur F5 (CLA), qui est nécessaire pour permettre l'intégration officielle du changement dans le référentiel principal.
La demande d'extraction est actuellement en statut Ouvrez, en attente de révision par les mainteneurs de NGINX, principalement Maxime Dounin et l'équipe technique F5.
Conformément à la pratique courante, l'intégration n'aura lieu qu'après une révision manuelle et une vérification interne du code, mais les premiers retours de la communauté sont déjà positifs, car le correctif comble une lacune pratique que de nombreux administrateurs avaient signalée de manière informelle.
Contributions et philosophie Open Source
Chez Managed Server Srl, nous croyons fermement que l'open source n'est pas seulement une base technologique, mais un responsabilité partagée.
De nombreux problèmes que nous rencontrons quotidiennement dans la gestion d'un hébergement haute performance sont résolus grâce à de petits correctifs, optimisations ou correctifs qui, une fois partagés, deviennent des améliorations globales.
Redonner ces correctifs à la communauté signifie faire évoluer l’écosystème qui profite à nous tous.
Le correctif proposé n'introduit pas de nouvelles directives, ne modifie pas le comportement par défaut et n'a pas d'impact sur les performances ; il corrige simplement un problème. manque de logique dans le flux d'analyse d'en-tête HTTP/3, rendant le code plus cohérent et prévisible.
En attendant la fusion officielle
La demande d'extraction n° 917 reste ouverte et en cours d'examen pour le moment.
Conformément à la pratique courante, l’équipe NGINX effectuera une révision manuelle du code, vérifiant la compatibilité avec les autres composants principaux et le respect des normes de développement internes.
Une fois approuvé, le changement sera intégré dans le branche principale et ensuite publié dans la prochaine version stable.
Cela garantira que toutes les futures versions de NGINX, y compris celles distribuées par les principaux mainteneurs Linux, incluront nativement le correctif, sans avoir besoin de correctifs manuels.
conclusion
Le correctif de bogue lié à $http_host HTTP/3 représente un petit pas concret vers un NGINX plus solide, cohérent et compatible avec les nouveaux standards du Web.
L’intervention démontre comment même les Les entreprises italiennes peuvent contribuer activement à des projets de niveau mondial, participant non seulement en tant qu'utilisateurs, mais en tant qu'acteurs directs dans le développement de l'open source.
Pour ceux qui souhaitent en savoir plus ou tester le patch en attendant la fusion officielle, le code est disponible publiquement dans la pull request :
https://github.com/nginx/nginx/pull/917
En tant que Managed Server Srl, nous continuerons à suivre l'évolution du module NGINX HTTP/3 et à partager toute amélioration ou optimisation supplémentaire qui pourrait découler de notre travail quotidien sur l'hébergement haute performance, les systèmes Linux et les environnements Web complexes.
En attendant, ce patch représente une contribution concrète à la stabilité et à la prévisibilité de l’un des composants les plus critiques de l’infrastructure Internet moderne.
