Indice dei contenuti dell'articolo:
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
elocation
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
.
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:
- Ambiente di sviluppo isolato, per verificare la compilazione e il comportamento del modulo
http_v3
. - Ambiente di staging, con simulazioni di traffico HTTP/3 e strumenti come
curl --http3
,h2load
,wrk
eautocannon
. - 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:
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.