hyperium/hyper · error · hyper::Error

invalid HTTP version parsed (found HTTP2 preface)

Error message

invalid HTTP version parsed (found HTTP2 preface)

What it means

Thrown via Error::new_version_h2() (src/error.rs:377, Kind::Parse(Parse::VersionH2)), HTTP/1-only feature. It is produced in Conn::on_parse_error (proto/h1/conn.rs:821) specifically when the connection's read buffer starts with the HTTP/2 connection preface (H2_PREFACE = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", conn.rs:29, detected by has_h2_prefix at conn.rs:206). It means the peer spoke HTTP/2 to a listener configured for HTTP/1 only. Detect with Error::is_parse_version_h2().

Source

Thrown at src/error.rs:377

    }

    pub(super) fn new_canceled() -> Error {
        Error::new(Kind::Canceled)
    }

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_incomplete() -> Error {
        Error::new(Kind::IncompleteMessage)
    }

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_too_large() -> Error {
        Error::new(Kind::Parse(Parse::TooLarge))
    }

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_version_h2() -> Error {
        Error::new(Kind::Parse(Parse::VersionH2))
    }

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_unexpected_message() -> Error {
        Error::new(Kind::UnexpectedMessage)
    }

    #[cfg(all(
        any(feature = "client", feature = "server"),
        any(feature = "http1", feature = "http2")
    ))]
    pub(super) fn new_io(cause: std::io::Error) -> Error {
        Error::new(Kind::Io).with(cause)
    }

    #[cfg(any(
        all(feature = "http1", any(feature = "client", feature = "server")),
        all(feature = "http2", feature = "client")

View on GitHub (pinned to 084473f728)

Solutions

  1. Enable HTTP/2 on the listener (enable the http2 feature and build the server with the h2 handshake) so the preface is handled instead of rejected.
  2. If you only support HTTP/1, configure the client to use HTTP/1 (e.g. http2_only(false) / http1_only) so it never sends the preface.
  3. For TLS, set ALPN correctly (e.g. ["h2", "http/1.1"]) so negotiation lands on a mutually supported protocol.

Example fix

// before: server only speaks HTTP/1, rejects HTTP/2 clients
let mut http = hyper::server::conn::Http::new(); // http1 only

// after: enable HTTP/2 so the preface is accepted
// (requires the "http2" cargo feature)
let make_svc = hyper::server::conn::auto::Builder::new(tokio_io)
    .serve_connection(stream, svc);
Defensive patterns

Strategy: validation

Validate before calling

// Decide protocol up front and build the matching server/client.
// For TLS, configure ALPN so negotiation is authoritative:
let alpn = tokio_rustls::rustls::ServerConfig::builder()
    .with_no_client_auth()
    .with_protocols(&[b"h2" as &[u8], b"http/1.1"])
    .into();

Type guard

fn is_h2_preface_on_h1(err: &hyper::Error) -> bool {
    err.is_parse_version_h2()
}

Try / catch

match serve_connection(...).await {
    Err(e) if e.is_parse_version_h2() => {
        // peer needs h2; rebuild this listener with the http2 feature / auto Builder
    }
    other => other,
}

Prevention

When it happens

Trigger: An HTTP/2 client connects to a plain HTTP/1 server (no TLS ALPN, no h2 in the server Builder); a service built with Http::new() (http1 only) receives the PRI/SM preface; ALPN negotiated h2 but the server was only built for http1. The 24-byte preface is matched in conn.rs:208.

Common situations: Mixing up http1/http2 feature flags in Cargo (server built without the http2 feature, or only http1 enabled); a client hard-coded to h2 hitting an http1 endpoint; a reverse proxy upstreaming h2 to an h1 backend; misconfigured ALPN where the listener speaks h1 but the client chose h2.

Related errors


AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06). Data as JSON: /data/errors/a95da77278d269ff.json. Report an issue: GitHub.