hyperium/hyper · error · hyper::Error
keep-alive timed out
Error message
keep-alive timed out
What it means
Thrown by the HTTP/2 keep-alive machinery (KeepAliveTimedOut, src/proto/h2/ping.rs:507 Display, wrapped at :501 via crate_error). hyper sends an HTTP/2 PING after keep_alive_interval of inactivity; if no PONG (or any frame) arrives within keep_alive_timeout, the connection is considered dead and the error is surfaced. The underlying source is crate::error::TimedOut, Kind::Http2.
Source
Thrown at src/proto/h2/ping.rs:501
fn maybe_timeout(&mut self, cx: &mut task::Context<'_>) -> Result<(), KeepAliveTimedOut> {
match self.state {
KeepAliveState::PingSent => {
if Pin::new(&mut self.sleep).poll(cx).is_pending() {
return Ok(());
}
trace!("keep-alive timeout ({:?}) reached", self.timeout);
Err(KeepAliveTimedOut)
}
KeepAliveState::Init | KeepAliveState::Scheduled(..) => Ok(()),
}
}
}
// ===== impl KeepAliveTimedOut =====
impl KeepAliveTimedOut {
pub(super) fn crate_error(self) -> crate::Error {
crate::Error::new(crate::error::Kind::Http2).with(self)
}
}
impl fmt::Display for KeepAliveTimedOut {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("keep-alive timed out")
}
}
impl std::error::Error for KeepAliveTimedOut {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&crate::error::TimedOut)
}
}
View on GitHub (pinned to 084473f728)
Solutions
- Increase keep_alive_timeout (and/or keep_alive_interval) to tolerate the peer's real RTT/processing time.
- If the timeout is legitimately short, retry the request on a fresh connection (the old one is dead).
- Verify network path: ensure H2 PING frames aren't being dropped by a middlebox/proxy.
- Confirm the peer (server or client) and any intermediary support H2 keep-alive PINGs.
- Disable keep-alive probing (http2_keep_alive_interval = None) only if an external liveness check exists.
Example fix
// before: aggressive keep-alive timeout
let client = hyper::Client::builder()
.http2_keep_alive_interval(Some(Duration::from_secs(5)))
.keep_alive_timeout(Duration::from_millis(500)) // too tight -> error 35
.build_http::<hyper::Body>();
// after: tolerate slow peers and retry on timeout
let client = hyper::Client::builder()
.http2_keep_alive_interval(Some(Duration::from_secs(15)))
.keep_alive_timeout(Duration::from_secs(20))
.build_http::<hyper::Body>(); Defensive patterns
Strategy: retry
Validate before calling
// Configure keep-alive to match the slowest legitimate peer RTT/processing.
use std::time::Duration;
let client = hyper::Client::builder()
.http2_keep_alive_interval(Some(Duration::from_secs(15)))
.http2_keep_alive_timeout(Duration::from_secs(20)) // >= worst-case RTT
.http2_keep_alive_while_idle(true)
.build_http::<hyper::Body>(); Try / catch
// Distinguish keep-alive timeout from other h2 errors and retry idempotent calls.
async fn fetch_retry(client: &hyper::Client<...>, req: hyper::Request<hyper::Body>) -> Result<_, hyper::Error> {
for attempt in 0..3 {
match client.request(req.clone()).await {
Ok(resp) => return Ok(resp),
Err(e) => {
let is_timeout = e.source()
.and_then(|s| s.downcast_ref::<std::io::Error>())
.map(|io| matches!(io.kind(), std::io::ErrorKind::TimedOut))
.unwrap_or(false)
|| e.to_string().contains("keep-alive timed out");
if is_timeout && attempt < 2 {
tracing::warn!(attempt, "h2 keep-alive timeout; retrying on new conn");
continue;
}
return Err(e);
}
}
}
unreachable!()
} Prevention
- Set http2_keep_alive_timeout above the peer's worst-case RTT + processing time.
- Retry idempotent requests on a fresh connection when keep-alive times out (the old conn is dead).
- Verify middleboxes/proxies in the path actually forward H2 PING frames.
- For battery-constrained clients, consider http2_keep_alive_while_idle=false and rely on app-level liveness.
When it happens
Trigger: An HTTP/2 peer that stops responding (no PONG, no data/ack frames) for longer than keep_alive_timeout after hyper sent a keep-alive PING. Configured on the client via Client::builder().http2_keep_alive_interval(...).keep_alive_timeout(...) (and while_idle), and on the server analogously.
Common situations: A backend that hangs (deadlock, GC pause, overloaded event loop); a network blackhole (path MTU, firewall dropping PING frames); keep_alive_timeout set too low for a slow/high-latency peer; peer/proxy that doesn't implement H2 PING acks; mobile clients that sleep without closing the connection.
Related errors
- error reading a body from connection
- Invalid chunk size line: missing size digit
- Invalid chunk size line: Invalid Size
- Invalid chunk size linear white space
- invalid chunk extension contains newline
AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06).
Data as JSON: /data/errors/103337a790ef41c4.json.
Report an issue: GitHub.