hyperium/hyper · warning · hyper::Error

read header from client timeout

Error message

read header from client timeout

What it means

Thrown via Error::new_header_timeout() (src/error.rs:437, Kind::HeaderTimeout), HTTP/1 server only. It fires when the client does not send the full request head (request line + headers) within header_read_timeout — the timer (which requires a Timer set via Builder::timer) elapses while hyper is still Pending on the head parse (proto/h1/conn.rs:264). The default is 30s (server/conn/http1.rs:250). Detect with Error::is_timeout().

Source

Thrown at src/error.rs:437

        feature = "ffi"
    ))]
    pub(super) fn new_body_write_aborted() -> Error {
        Error::new(Kind::User(User::BodyWriteAborted))
    }

    fn new_user(user: User) -> Error {
        Error::new(Kind::User(user))
    }

    #[cfg(any(feature = "http1", feature = "http2"))]
    #[cfg(feature = "server")]
    pub(super) fn new_user_header() -> Error {
        Error::new_user(User::UnexpectedHeader)
    }

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

    #[cfg(feature = "http1")]
    #[cfg(feature = "server")]
    pub(super) fn new_user_unsupported_status_code() -> Error {
        Error::new_user(User::UnsupportedStatusCode)
    }

    pub(super) fn new_user_no_upgrade() -> Error {
        Error::new_user(User::NoUpgrade)
    }

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_user_manual_upgrade() -> Error {
        Error::new_user(User::ManualUpgrade)
    }

    #[cfg(any(

View on GitHub (pinned to 084473f728)

Solutions

  1. Ensure a Timer is installed (Builder::timer with tokio time) so the timeout actually takes effect, and pick a sane header_read_timeout for your clients.
  2. If legitimate slow clients trip it, raise header_read_timeout (or disable it) — but keep some limit to resist slowloris.
  3. Put a connection-level idle/read timeout in front (reverse proxy) as defense in depth.

Example fix

// before: header_read_timeout configured but no Timer => panic, or no effect
let mut http = Http::new();
http.header_read_timeout(std::time::Duration::from_secs(30));

// after: install the tokio Timer so the timeout is enforced
let mut http = Http::new();
http.with_upgrades();
http.timer(tokio_compat::Timer::new());
http.header_read_timeout(std::time::Duration::from_secs(30));
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm a Timer is installed before the server runs, otherwise the
// timeout silently does nothing (or panics if set without a Timer).
fn assert_timer_configured(b: &hyper::server::conn::http1::Builder) {
    // Builder::timer must have been called for header_read_timeout to take effect
    let _ = b; // your framework wrapper should expose/require the timer
}

Type guard

fn is_header_timeout(err: &hyper::Error) -> bool {
    err.is_timeout()
}

Try / catch

if let Err(e) = conn_fut.await {
    if e.is_timeout() {
        // client was too slow sending headers; just close
    } else {
        tracing::error!("conn error: {e}");
    }
}

Prevention

When it happens

Trigger: A client opens a TCP/TLS connection but never sends (or only slowly sends) the request line+headers; a port scanner / health check that opens a socket and idles; slowloris-style attack. The header timer at conn.rs:219-234 fires and poll_read_head returns Err(new_header_timeout()) at conn.rs:264.

Common situations: Forgotten to call Builder::timer (the timeout is a no-op without a Timer, and configuring it without a timer panics — see server/conn/http1.rs:346/480); load balancer health checks that hold connections open; misbehaving or malicious slow clients; default 30s too short for very slow uploaders.

Related errors


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