seanmonstar/reqwest · error · std::io::Error
HTTP/3 only supports 'https' or 'h3' schemes, got
Error message
HTTP/3 only supports 'https' or 'h3' schemes, got: {} What it means
Thrown by extract_domain() in the HTTP/3 connection pool (src/async_impl/h3_client/pool.rs:364-385) when a request routed to the h3 client carries a URI whose scheme is neither 'https' nor 'h3'. HTTP/3 runs over QUIC, which requires TLS 1.3, so plaintext 'http' URIs are fundamentally invalid for the h3 transport. The guard fires before any connection is opened and the offending scheme string is interpolated into the message.
Solutions
- Ensure the request URL uses the 'https://' scheme when forcing HTTP/3: replace 'http://host' with 'https://host' before calling .version(http::Version::HTTP_3).
- Remove the forced .version(http::Version::HTTP_3) and let reqwest negotiate the protocol; only the server's ALPN-advertised h3 over QUIC will actually use HTTP/3.
- If you must use a custom scheme internally, register an 'h3' URI scheme and pass an 'h3://host' URL, since pool.rs:369 explicitly accepts 'h3'.
- Add a runtime guard that asserts url.scheme() == "https" before constructing an HTTP/3 request so the failure surfaces at call site.
Example fix
// before
let resp = client
.get("http://api.example.com/data")
.version(http::Version::HTTP_3)
.send()
.await?;
// after
let resp = client
.get("https://api.example.com/data")
.version(http::Version::HTTP_3)
.send()
.await?; Defensive patterns
Strategy: validation
Validate before calling
fn assert_h3_compatible(url: &url::Url) -> Result<(), &'static str> {
match url.scheme() {
"https" | "h3" => Ok(()),
other => Err("HTTP/3 requires an https:// or h3:// URL"),
}
}
// call before .version(http::Version::HTTP_3)
let url = url::Url::parse(&raw_url)?;
assert_h3_compatible(&url)?; Type guard
fn is_h3_scheme(url: &url::Url) -> bool {
matches!(url.scheme(), "https" | "h3")
} Try / catch
// reqwest::Error has no public kind() for this; match on the Display source.
if let Err(e) = resp {
if e.to_string().contains("HTTP/3 only supports") {
// downgrade to HTTP/2/1.1 over TLS and retry
} else {
return Err(e);
}
} Prevention
- Centralize HTTP/3 request construction in one helper that enforces https:// and never accepts raw strings.
- Wire a unit test that asserts every .version(HTTP_3) call site targets an https URL.
- When enabling the http3 feature, document that all forced-HTTP/3 endpoints must be TLS endpoints.
When it happens
Trigger: Building a Client with the 'http3' feature and forcing .version(http::Version::HTTP_3) on a RequestBuilder pointed at an 'http://' URL. Also occurs when HttpVersionPref::Http3 / .http3_prior_config() is set but the request URL is plaintext http. The check at pool.rs:369 compares scheme.as_str() strictly to 'https' or 'h3', so any other scheme (http, ws, ftp) on a request dispatched to the h3 client triggers it.
Common situations: Mixing .version(http::Version::HTTP_3) with a non-TLS endpoint during local testing against 'http://localhost'. Misconfigured base URLs in env vars (HTTP where HTTPS is required). A load balancer terminating TLS and forwarding plain http URLs while the client is pinned to HTTP/3. Feature-flag mismatch: enabling 'http3' but targeting servers that only serve HTTP/1.1 or h2 cleartext.
Related errors
AI-assisted analysis of seanmonstar/reqwest@9f06fd28ab (2026-08-10).
Data as JSON: /api/errors/23cfb8016bdd1fc9.
Report an issue: GitHub.
Appendix: source
Thrown at src/async_impl/h3_client/pool.rs:370
}
}
fn size_hint(&self) -> hyper::body::SizeHint {
if let Some(content_length) = self.content_length {
hyper::body::SizeHint::with_exact(content_length)
} else {
hyper::body::SizeHint::default()
}
}
}
pub(crate) fn extract_domain(uri: &mut Uri) -> Result<Key, Error> {
let uri_clone = uri.clone();
match (uri_clone.scheme(), uri_clone.authority()) {
(Some(scheme), Some(auth)) => {
let scheme_str = scheme.as_str();
if scheme_str != "https" && scheme_str != "h3" {
return Err(Error::new(
Kind::Request,
Some(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"HTTP/3 only supports 'https' or 'h3' schemes, got: {}",
scheme_str
),
))),
));
}
Ok((scheme.clone(), auth.clone()))
}
_ => Err(Error::new(Kind::Request, None::<Error>)),
}
}
pub(crate) fn domain_as_uri((scheme, auth): Key) -> Uri {
http::uri::Builder::new()View on GitHub (pinned to 9f06fd28ab)