5 Ottobre 2025

Corretto un bug e proposta patch in NGINX HTTP/3 che impediva la valorizzazione di $http_host

La patch di Managed Server Srl è ora in revisione per il merge nel master branch ufficiale di NGINX

Patch-NGINX-Http3-http_host

Negli ultimi mesi il protocollo HTTP/3, basato su QUIC, sta entrando sempre più spesso in produzione. I principali browser lo supportano stabilmente e molti provider — compresi Cloudflare, Google e Akamai — lo considerano ormai lo standard de facto per le connessioni moderne. Anche NGINX, dalla serie 1.25 in poi, ha introdotto un modulo sperimentale HTTP/3, poi reso più stabile con la 1.29.x.
Tuttavia, durante alcune sessioni di testing approfondito condotte da Managed Server Srl, abbiamo individuato un comportamento anomalo che può generare incoerenze nei rewrite, nei log e nelle variabili d’ambiente, in particolare per siti e applicazioni che si affidano alla variabile $http_host.

Il problema nasceva da una mancata valorizzazione di questa variabile durante la gestione delle richieste HTTP/3, ossia in connessioni basate su QUIC. A differenza di HTTP/1.1 e HTTP/2, dove l’header Host viene automaticamente interpretato e associato, nel caso di HTTP/3 NGINX non effettuava la corretta inizializzazione del valore corrispondente quando il client inviava soltanto l’header pseudo :authority.
Il risultato? $http_host restava vuoto o non coerente con il valore dell’authority richiesta, compromettendo la compatibilità con molte configurazioni e generando comportamenti imprevisti in diversi scenari applicativi.

La variabile $http_host e la sua importanza

Per comprendere appieno la portata del problema, vale la pena ricordare che $http_host è una delle variabili più utilizzate in ambiente NGINX.
Viene spesso impiegata nei:

  • blocchi server e location con logiche condizionali;
  • rewrite dinamici basati su host;
  • regole di redirect verso domini canonici;
  • logging personalizzato;
  • proxy pass che dipendono dall’host della richiesta originale.

In configurazioni multi-dominio o multi-tenant, l’assenza o l’incoerenza del valore di $http_host può alterare il comportamento dell’intero stack, fino a far rispondere un sito con il dominio sbagliato o impedire il corretto funzionamento dei redirect HTTPS.

La causa: il comportamento di NGINX con HTTP/3

Secondo la RFC 9114, Sezione 4.2 (“Request Pseudo-Header Fields”), ogni richiesta HTTP/3 deve contenere un campo :authority oppure un header Host.
Entrambi non possono mancare, e se presenti devono contenere lo stesso valore.
Tuttavia, la maggior parte dei client HTTP/3 (come Chrome, Firefox e cURL nella modalità --http3) invia solo il campo :authority, senza duplicarlo come Host.

rfc9114-http3-pseudo-header

NGINX, fino alla versione 1.29.1, non trasferiva automaticamente quel valore nel corrispondente header interno Host, da cui viene poi derivata la variabile $http_host.
Questo causava uno scarto funzionale tra HTTP/1.1 / HTTP/2 e HTTP/3: nei primi due casi $http_host era sempre disponibile, nel terzo no.

Il comportamento, pur conforme alle specifiche, non era coerente con la filosofia operativa di NGINX, dove le variabili d’ambiente devono mantenere uniformità tra protocolli, così da non costringere l’amministratore a differenziare configurazioni tra HTTP/2 e HTTP/3.

La patch: un’integrazione semplice ma fondamentale

Dopo un’attenta analisi del codice sorgente di NGINX, e prendendo spunto da una correzione analoga già presente in ANGIE, il fork sviluppato da Web Server LLC, abbiamo realizzato una patch minimale ma risolutiva.
La modifica, proposta da Marco Marcoaldi (CTO di Managed Server Srl), consiste nell’aggiunta di una funzione dedicata:

ngx_http_v3_set_host(ngx_http_request_t *r, ngx_str_t *value)

Questa funzione si occupa di inizializzare il campo r->headers_in.host quando non è presente ma esiste il campo :authority, copiandone il valore in modo sicuro.
Il codice, integrato nel file sorgente src/http/v3/ngx_http_v3_request.c, utilizza la struttura dati interna di NGINX per allocare un nuovo header “host” coerente con le richieste HTTP/3 in arrivo.

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)
     ...
+

 

La patch include inoltre un controllo logico aggiuntivo: se entrambi i campi Host e :authority sono presenti e contengono valori diversi, viene registrato un warning nel log (NGX_LOG_INFO) e la richiesta viene rigettata, come previsto dalle specifiche RFC.
In pratica, viene assicurata la piena equivalenza semantica tra Host e :authority, eliminando ogni possibile ambiguità.

Testing e validazione in ambiente reale

Una volta completata la modifica, la patch è stata testata in tre fasi:

  1. Ambiente di sviluppo isolato, per verificare la compilazione e il comportamento del modulo http_v3.
  2. Ambiente di staging, con simulazioni di traffico HTTP/3 e strumenti come curl --http3, h2load, wrk e autocannon.
  3. Ambiente di produzione su un sito Magento 2, utilizzando il build nginx-1.29.1 con il flag --with-http_v3_module.

In tutti i test, la variabile $http_host è risultata correttamente valorizzata con il valore derivato dal campo :authority, anche in assenza di header Host.
Non si sono verificati regressioni né impatti sulle performance, nemmeno sotto carico sostenuto o con connessioni simultanee su QUIC.
Le risposte HTTP hanno mantenuto latenza e throughput invariati rispetto alla build originale.

Proposta di merge nel master branch ufficiale

Consolidato il comportamento e verificata la compatibilità, abbiamo deciso di rendere pubblica la modifica.
È stato quindi eseguito il fork del repository ufficiale di NGINX su GitHub e aperta la Pull Request #917, visibile qui:


HTTP/3: initialize Host header from :authority to enable $http_host variable – Pull Request #917

La PR è corredata da descrizione tecnica, riferimenti alla RFC 9114, codice diff completo e note sui test di validazione in produzione.
Contestualmente è stato firmato il F5 Contributor License Agreement (CLA), necessario per consentire l’integrazione ufficiale della modifica nel repository principale.

Attualmente la pull request è in stato Open, in attesa di revisione da parte dei maintainer NGINX, principalmente Maxim Dounin e il team tecnico di F5.
Come da prassi, l’integrazione avverrà solo dopo revisione manuale e verifica interna del codice, ma il feedback preliminare della community è già positivo, poiché la correzione risolve una lacuna pratica che molti amministratori avevano segnalato informalmente.

Contributi e filosofia Open Source

In Managed Server Srl crediamo fortemente che l’open source non sia solo una base tecnologica, ma una responsabilità condivisa.
Molti dei problemi che incontriamo quotidianamente nella gestione di hosting ad alte prestazioni si risolvono proprio grazie a piccole patch, ottimizzazioni o fix che, una volta condivisi, diventano miglioramenti globali.
Restituire queste correzioni alla community significa far evolvere l’ecosistema di cui tutti beneficiamo.

La patch proposta non introduce nuove direttive, non altera il comportamento di default e non impatta le performance; si limita a correggere una mancanza logica nel flusso di parsing degli header HTTP/3, rendendo il codice più coerente e prevedibile.

In attesa del merge ufficiale

In questo momento la Pull Request #917 rimane aperta e in revisione.
Come da prassi, il team NGINX eseguirà una revisione manuale del codice, verificando la compatibilità con le altre componenti del core e l’aderenza agli standard interni di sviluppo.
Una volta approvata, la modifica verrà integrata nel master branch e quindi rilasciata nella successiva versione stabile.
Questo garantirà che tutte le future build di NGINX — incluse quelle distribuite dai principali maintainer Linux — includano nativamente il fix, senza la necessità di patch manuali.

Conclusione

La correzione del bug legato a $http_host in HTTP/3 rappresenta un piccolo ma concreto passo avanti verso un NGINX più solido, coerente e compatibile con i nuovi standard del web.
L’intervento dimostra come anche le aziende italiane possano contribuire attivamente a progetti di livello globale, partecipando non solo come utenti, ma come attori diretti dello sviluppo open source.

Per chi desidera approfondire o testare la patch in attesa del merge ufficiale, il codice è disponibile pubblicamente nella pull request:

https://github.com/nginx/nginx/pull/917

Come Managed Server Srl, continueremo a monitorare l’evoluzione del modulo HTTP/3 di NGINX e a condividere eventuali ulteriori miglioramenti o ottimizzazioni che possano nascere dal nostro lavoro quotidiano su hosting ad alte prestazioni, sistemi Linux e ambienti web complessi.
Nel frattempo, questa patch rappresenta un contributo concreto alla stabilità e alla prevedibilità di uno dei componenti più critici dell’infrastruttura Internet moderna.

Hai dei dubbi? Non sai da dove iniziare? Contattaci !

Abbiamo tutte le risposte alle tue domande per aiutarti nella giusta scelta.

Chatta con noi

Chatta direttamente con il nostro supporto prevendita.

0256569681

Contattaci telefonicamente negli orari d’ufficio 9:30 – 19:30

Contattaci online

Apri una richiesta direttamente nell’area dei contatti.

DISCLAIMER, Note Legali e Copyright. Red Hat, Inc. detiene i diritti su Red Hat®, RHEL®, RedHat Linux®, e CentOS®; AlmaLinux™ è un marchio di AlmaLinux OS Foundation; Rocky Linux® è un marchio registrato di Rocky Linux Foundation; SUSE® è un marchio registrato di SUSE LLC; Canonical Ltd. detiene i diritti su Ubuntu®; Software in the Public Interest, Inc. detiene i diritti su Debian®; Linus Torvalds detiene i diritti su Linux®; FreeBSD® è un marchio registrato di The FreeBSD Foundation; NetBSD® è un marchio registrato di The NetBSD Foundation; OpenBSD® è un marchio registrato di Theo de Raadt; Oracle Corporation detiene i diritti su Oracle®, MySQL®, MyRocks®, VirtualBox® e ZFS®; Percona® è un marchio registrato di Percona LLC; MariaDB® è un marchio registrato di MariaDB Corporation Ab; PostgreSQL® è un marchio registrato di PostgreSQL Global Development Group; SQLite® è un marchio registrato di Hipp, Wyrick & Company, Inc.; KeyDB® è un marchio registrato di EQ Alpha Technology Ltd.; Typesense® è un marchio registrato di Typesense Inc.; REDIS® è un marchio registrato di Redis Labs Ltd; F5 Networks, Inc. detiene i diritti su NGINX® e NGINX Plus®; Varnish® è un marchio registrato di Varnish Software AB; HAProxy® è un marchio registrato di HAProxy Technologies LLC; Traefik® è un marchio registrato di Traefik Labs; Envoy® è un marchio registrato di CNCF; Adobe Inc. detiene i diritti su Magento®; PrestaShop® è un marchio registrato di PrestaShop SA; OpenCart® è un marchio registrato di OpenCart Limited; Automattic Inc. detiene i diritti su WordPress®, WooCommerce®, e JetPack®; Open Source Matters, Inc. detiene i diritti su Joomla®; Dries Buytaert detiene i diritti su Drupal®; Shopify® è un marchio registrato di Shopify Inc.; BigCommerce® è un marchio registrato di BigCommerce Pty. Ltd.; TYPO3® è un marchio registrato di TYPO3 Association; Ghost® è un marchio registrato di Ghost Foundation; Amazon Web Services, Inc. detiene i diritti su AWS® e Amazon SES®; Google LLC detiene i diritti su Google Cloud™, Chrome™, e Google Kubernetes Engine™; Alibaba Cloud® è un marchio registrato di Alibaba Group Holding Limited; DigitalOcean® è un marchio registrato di DigitalOcean, LLC; Linode® è un marchio registrato di Linode, LLC; Vultr® è un marchio registrato di The Constant Company, LLC; Akamai® è un marchio registrato di Akamai Technologies, Inc.; Fastly® è un marchio registrato di Fastly, Inc.; Let’s Encrypt® è un marchio registrato di Internet Security Research Group; Microsoft Corporation detiene i diritti su Microsoft®, Azure®, Windows®, Office®, e Internet Explorer®; Mozilla Foundation detiene i diritti su Firefox®; Apache® è un marchio registrato di The Apache Software Foundation; Apache Tomcat® è un marchio registrato di The Apache Software Foundation; PHP® è un marchio registrato del PHP Group; Docker® è un marchio registrato di Docker, Inc.; Kubernetes® è un marchio registrato di The Linux Foundation; OpenShift® è un marchio registrato di Red Hat, Inc.; Podman® è un marchio registrato di Red Hat, Inc.; Proxmox® è un marchio registrato di Proxmox Server Solutions GmbH; VMware® è un marchio registrato di Broadcom Inc.; CloudFlare® è un marchio registrato di Cloudflare, Inc.; NETSCOUT® è un marchio registrato di NETSCOUT Systems Inc.; ElasticSearch®, LogStash®, e Kibana® sono marchi registrati di Elastic N.V.; Grafana® è un marchio registrato di Grafana Labs; Prometheus® è un marchio registrato di The Linux Foundation; Zabbix® è un marchio registrato di Zabbix LLC; Datadog® è un marchio registrato di Datadog, Inc.; Ceph® è un marchio registrato di Red Hat, Inc.; MinIO® è un marchio registrato di MinIO, Inc.; Mailgun® è un marchio registrato di Mailgun Technologies, Inc.; SendGrid® è un marchio registrato di Twilio Inc.; Postmark® è un marchio registrato di ActiveCampaign, LLC; cPanel®, L.L.C. detiene i diritti su cPanel®; Plesk® è un marchio registrato di Plesk International GmbH; Hetzner® è un marchio registrato di Hetzner Online GmbH; OVHcloud® è un marchio registrato di OVH Groupe SAS; Terraform® è un marchio registrato di HashiCorp, Inc.; Ansible® è un marchio registrato di Red Hat, Inc.; cURL® è un marchio registrato di Daniel Stenberg; Facebook®, Inc. detiene i diritti su Facebook®, Messenger® e Instagram®. Questo sito non è affiliato, sponsorizzato o altrimenti associato a nessuna delle entità sopra menzionate e non rappresenta nessuna di queste entità in alcun modo. Tutti i diritti sui marchi e sui nomi di prodotto menzionati sono di proprietà dei rispettivi detentori di copyright. Ogni altro marchio citato appartiene ai propri registranti. MANAGED SERVER® è un marchio registrato a livello europeo da MANAGED SERVER SRL, con sede legale in Via Flavio Gioia, 6, 62012 Civitanova Marche (MC), Italia e sede operativa in Via Enzo Ferrari, 9, 62012 Civitanova Marche (MC), Italia.

SOLO UN ATTIMO !

Ti sei mai chiesto se il tuo Hosting faccia schifo ?

Scopri subito se il tuo hosting provider ti sta danneggiando con un sito lento degno del 1990 ! Risultato immediato.

Close the CTA
Torna in alto