October 5, 2025

Fixed a bug and proposed patch in NGINX HTTP/3 that prevented $http_host from being valued

The Managed Server Srl patch is now under review for merging into the official NGINX master branch.

Patch-NGINX-Http3-http_host

In recent months the protocol HTTP / 3, based on HERE C, is increasingly entering production. Major browsers support it stably, and many providers—including Cloudflare, Google, and Akamai—now consider it the de facto standard for modern connections. Even Nginx, from the 1.25 series onwards, introduced an experimental HTTP/3 module, later made more stable with 1.29.x.
However, during some in-depth testing sessions conducted by Managed Server Srl, we have identified an anomalous behavior that can generate inconsistencies in rewrites, logs, and environment variables, especially for sites and applications that rely on the variable $http_host.

The problem arose from a failure to value this variable when handling HTTP/3 requests, i.e., QUIC-based connections. Unlike HTTP/1.1 and HTTP/2, where the header Host is automatically interpreted and associated, in the case of HTTP/3 NGINX did not perform the correct initialization of the corresponding value when the client sent only the pseudo header :authority.
The result? $http_host remained empty or inconsistent with the value of the requested authority, compromising compatibility with many configurations and generating unexpected behavior in several application scenarios.

The variable $http_host and its importance

To fully understand the extent of the problem, it is worth remembering that $http_host It is one of the most used variables in the NGINX environment.
It is often used in:

  • blocks server e location with conditional logic;
  • host-based dynamic rewrites;
  • redirect rules to canonical domains;
  • custom logging;
  • proxy passes that depend on the host of the original request.

In multi-domain or multi-tenant configurations, the absence or inconsistency of the value of $http_host It can alter the behavior of the entire stack, causing a site to respond with the wrong domain or preventing HTTPS redirects from working properly.

The cause: NGINX's behavior with HTTP/3

According to RFC 9114, Section 4.2 (“Request Pseudo-Header Fields”), every HTTP/3 request must contain a field :authority or a header Host.
Both cannot be missing, and if present they must contain the same value.
However, most HTTP/3 clients (such as Chrome, Firefox, and cURL in --http3) send field only :authority, without duplicating it as Host.

rfc9114-http3-pseudo-header

NGINX, up to version 1.29.1, did not automatically pass that value into the corresponding internal header Host, from which the variable is then derived $http_host.
This caused a functional gap between HTTP/1.1 / HTTP/2 and HTTP/3: in the first two cases $http_host he was always available, in the third he wasn't.

The behavior, although compliant with the specifications, it was not consistent with the operating philosophy of NGINX, where environment variables must maintain uniformity between protocols, so as not to force the administrator to differentiate configurations between HTTP/2 and HTTP/3.

The Patch: A Simple but Essential Integration

After a careful analysis of the NGINX source code, and taking inspiration from a similar fix already present in ANGIE, the fork developed by Web Server LLC, we have created a minimal but effective patch.
The amendment, proposed by Marco Marcoaldi (CTO of Managed Server Srl), consists in the addition of a dedicated function:

ngx_http_v3_set_host(ngx_http_request_t *r, ngx_str_t *value)

This function takes care of initializing the field r->headers_in.host when the field is not present but exists :authority, copying its value safely.
The code, integrated into the source file src/http/v3/ngx_http_v3_request.c, uses NGINX's internal data structure to allocate a new “host” header consistent with incoming HTTP/3 requests.

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

 

The patch also includes an additional logical check: if both fields Host e :authority are present and contain different values, a warning is recorded in the log (NGX_LOG_INFO) and the request is rejected, as required by the RFC specifications.
In practice, the full semantic equivalence between Host e :authority, eliminating any possible ambiguity.

Testing and validation in real environment

Once the modification was completed, the patch was tested in three phases:

  1. Isolated development environment, to check the compilation and behavior of the form http_v3.
  2. Staging environment, with HTTP/3 traffic simulations and tools like curl --http3, h2load, wrk e autocannon.
  3. Production environment on a Magento 2 site, using the build nginx-1.29.1 with the flag --with-http_v3_module.

In all tests, the variable $http_host it was correctly valued with the value derived from the field :authority, even in the absence of headers Host.
There were no performance regressions or impacts, even under sustained load or with concurrent connections on QUIC.
HTTP responses maintained latency and throughput unchanged from the original build.

Merge proposal into the official master branch

Once we had consolidated the behavior and verified the compatibility, we decided to make the change public.
It was then performed fork of the official NGINX repository on GitHub and opened the Pull Request #917, visible here:


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

The PR is accompanied by a technical description, references to RFC 9114, complete diff code, and notes on production validation tests.
At the same time the agreement was signed F5 Contributor License Agreement (CLA), which is required to allow the change to be officially integrated into the main repository.

The pull request is currently in status Open, pending review by NGINX maintainers, primarily Maxim Dounin and the F5 technical team.
As per standard practice, the integration will only take place after manual review and internal verification of the code, but preliminary feedback from the community is already positive, as the fix addresses a practical gap that many administrators had informally reported.

Open Source Contributions and Philosophy

At Managed Server Srl we strongly believe that open source is not just a technological basis, but a shared responsibility.
Many of the problems we encounter daily in managing high-performance hosting are solved thanks to small patches, optimizations, or fixes that, once shared, become global improvements.
Giving these fixes back to the community means evolving the ecosystem that benefits us all.

The proposed patch does not introduce new directives, does not alter the default behavior and does not impact performance; it simply corrects a logical lack in the HTTP/3 header parsing stream, making the code more consistent and predictable.

Waiting for the official merger

Pull Request #917 remains open and under review at this time.
As per standard practice, the NGINX team will perform a manual review of the code, verifying compatibility with other core components and adherence to internal development standards.
Once approved, the change will be incorporated into the master branch and then released in the next stable release.
This will ensure that all future NGINX builds — including those distributed by major Linux maintainers — will natively include the fix, without the need for manual patches.

Conclusion

The bug fix related to $http_host in HTTP/3 represents a small but concrete step forward towards a more solid, consistent and compatible NGINX with the new web standards.
The intervention demonstrates how even the Italian companies can actively contribute to global level projects, participating not only as users, but as direct actors in open source development.

For those who want to learn more or test the patch while waiting for the official merge, the code is publicly available in the pull request:

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

As Managed Server Srl, we will continue to monitor the evolution of the NGINX HTTP/3 module and share any further improvements or optimizations that may arise from our daily work on high-performance hosting, Linux systems and complex web environments.
In the meantime, this patch represents a concrete contribution to the stability and predictability of one of the most critical components of the modern Internet infrastructure.

Do you have doubts? Don't know where to start? Contact us!

We have all the answers to your questions to help you make the right choice.

Chat with us

Chat directly with our presales support.

0256569681

Contact us by phone during office hours 9:30 - 19:30

Contact us online

Open a request directly in the contact area.

DISCLAIMER, Legal Notes and Copyright. RedHat, Inc. holds the rights to Red Hat®, RHEL®, RedHat Linux®, and CentOS®; AlmaLinux™ is a trademark of the AlmaLinux OS Foundation; Rocky Linux® is a registered trademark of the Rocky Linux Foundation; SUSE® is a registered trademark of SUSE LLC; Canonical Ltd. holds the rights to Ubuntu®; Software in the Public Interest, Inc. holds the rights to Debian®; Linus Torvalds holds the rights to Linux®; FreeBSD® is a registered trademark of The FreeBSD Foundation; NetBSD® is a registered trademark of The NetBSD Foundation; OpenBSD® is a registered trademark of Theo de Raadt; Oracle Corporation holds the rights to Oracle®, MySQL®, MyRocks®, VirtualBox®, and ZFS®; Percona® is a registered trademark of Percona LLC; MariaDB® is a registered trademark of MariaDB Corporation Ab; PostgreSQL® is a registered trademark of PostgreSQL Global Development Group; SQLite® is a registered trademark of Hipp, Wyrick & Company, Inc.; KeyDB® is a registered trademark of EQ Alpha Technology Ltd.; Typesense® is a registered trademark of Typesense Inc.; REDIS® is a registered trademark of Redis Labs Ltd; F5 Networks, Inc. owns the rights to NGINX® and NGINX Plus®; Varnish® is a registered trademark of Varnish Software AB; HAProxy® is a registered trademark of HAProxy Technologies LLC; Traefik® is a registered trademark of Traefik Labs; Envoy® is a registered trademark of CNCF; Adobe Inc. owns the rights to Magento®; PrestaShop® is a registered trademark of PrestaShop SA; OpenCart® is a registered trademark of OpenCart Limited; Automattic Inc. holds the rights to WordPress®, WooCommerce®, and JetPack®; Open Source Matters, Inc. owns the rights to Joomla®; Dries Buytaert owns the rights to Drupal®; Shopify® is a registered trademark of Shopify Inc.; BigCommerce® is a registered trademark of BigCommerce Pty. Ltd.; TYPO3® is a registered trademark of the TYPO3 Association; Ghost® is a registered trademark of the Ghost Foundation; Amazon Web Services, Inc. owns the rights to AWS® and Amazon SES®; Google LLC owns the rights to Google Cloud™, Chrome™, and Google Kubernetes Engine™; Alibaba Cloud® is a registered trademark of Alibaba Group Holding Limited; DigitalOcean® is a registered trademark of DigitalOcean, LLC; Linode® is a registered trademark of Linode, LLC; Vultr® is a registered trademark of The Constant Company, LLC; Akamai® is a registered trademark of Akamai Technologies, Inc.; Fastly® is a registered trademark of Fastly, Inc.; Let's Encrypt® is a registered trademark of the Internet Security Research Group; Microsoft Corporation owns the rights to Microsoft®, Azure®, Windows®, Office®, and Internet Explorer®; Mozilla Foundation owns the rights to Firefox®; Apache® is a registered trademark of The Apache Software Foundation; Apache Tomcat® is a registered trademark of The Apache Software Foundation; PHP® is a registered trademark of the PHP Group; Docker® is a registered trademark of Docker, Inc.; Kubernetes® is a registered trademark of The Linux Foundation; OpenShift® is a registered trademark of Red Hat, Inc.; Podman® is a registered trademark of Red Hat, Inc.; Proxmox® is a registered trademark of Proxmox Server Solutions GmbH; VMware® is a registered trademark of Broadcom Inc.; CloudFlare® is a registered trademark of Cloudflare, Inc.; NETSCOUT® is a registered trademark of NETSCOUT Systems Inc.; ElasticSearch®, LogStash®, and Kibana® are registered trademarks of Elastic NV; Grafana® is a registered trademark of Grafana Labs; Prometheus® is a registered trademark of The Linux Foundation; Zabbix® is a registered trademark of Zabbix LLC; Datadog® is a registered trademark of Datadog, Inc.; Ceph® is a registered trademark of Red Hat, Inc.; MinIO® is a registered trademark of MinIO, Inc.; Mailgun® is a registered trademark of Mailgun Technologies, Inc.; SendGrid® is a registered trademark of Twilio Inc.; Postmark® is a registered trademark of ActiveCampaign, LLC; cPanel®, LLC owns the rights to cPanel®; Plesk® is a registered trademark of Plesk International GmbH; Hetzner® is a registered trademark of Hetzner Online GmbH; OVHcloud® is a registered trademark of OVH Groupe SAS; Terraform® is a registered trademark of HashiCorp, Inc.; Ansible® is a registered trademark of Red Hat, Inc.; cURL® is a registered trademark of Daniel Stenberg; Facebook®, Inc. owns the rights to Facebook®, Messenger® and Instagram®. This site is not affiliated with, sponsored by, or otherwise associated with any of the above-mentioned entities and does not represent any of these entities in any way. All rights to the brands and product names mentioned are the property of their respective copyright holders. All other trademarks mentioned are the property of their respective registrants.

JUST A MOMENT !

Have you ever wondered if your hosting sucks?

Find out now if your hosting provider is hurting you with a slow website worthy of 1990! Instant results.

Close the CTA
Back to top