Problem
When a user-provided Host header is present on an outbound HTTP/2 request, h2 emits it as a regular header field alongside the :authority pseudo-header derived from the URI. If the two values differ, this produces contradictory authority metadata on the wire.
For example, sending a request with URI https://example.net/ and header Host: example.com results in a HEADERS frame containing both :authority: example.net and host: example.com.
Expected behavior
Per RFC 9113 §8.3.1, clients must not generate a request containing inconsistent value in Host and :authority. The simplest way to guarantee consistency is to never emit host as a regular header on the wire for HTTP/2.
Other HTTP/2 implementations do this. For instance, curl with an explicit Host header (curl -H 'host: foo.net' https://bar.com) promotes the user-supplied Host value to :authority and strips host from regular headers entirely (verified via Wireshark). Go's net/http and Python's httpx behave the same way.
Additional info
This issue is evident in Deno's fetch implementation, which is implemented with the hyper stack (including h2). We expect the following code to send a request with :authority = example.net (instead of example.com), but what's actually sent with Deno v2.6.9 is :authority = example.com and host = example.net, violating the HTTP/2 spec.
using client = Deno.createHttpClient({ allowHost: true });
const res = await fetch("https://example.com", {
client,
headers: {
host: "example.net",
},
});
console.log(res.status);
Problem
When a user-provided
Hostheader is present on an outbound HTTP/2 request, h2 emits it as a regular header field alongside the:authoritypseudo-header derived from the URI. If the two values differ, this produces contradictory authority metadata on the wire.For example, sending a request with URI
https://example.net/and headerHost: example.comresults in a HEADERS frame containing both:authority: example.netandhost: example.com.Expected behavior
Per RFC 9113 §8.3.1, clients must not generate a request containing inconsistent value in
Hostand:authority. The simplest way to guarantee consistency is to never emithostas a regular header on the wire for HTTP/2.Other HTTP/2 implementations do this. For instance,
curlwith an explicit Host header (curl -H 'host: foo.net' https://bar.com) promotes the user-suppliedHostvalue to:authorityand stripshostfrom regular headers entirely (verified via Wireshark). Go'snet/httpand Python'shttpxbehave the same way.Additional info
This issue is evident in Deno's
fetchimplementation, which is implemented with the hyper stack (including h2). We expect the following code to send a request with:authority = example.net(instead ofexample.com), but what's actually sent with Deno v2.6.9 is:authority = example.comandhost = example.net, violating the HTTP/2 spec.