DioxusLabs/dioxus · error · anyhow::Error

HTTP handshake failed: {e}

Error message

HTTP handshake failed: {e}

What it means

After a successful TCP connect, send_with_retry performs a plaintext HTTP/1.1 handshake on that socket via hyper's http1::handshake. If the peer does not speak HTTP/1.1 in the clear — a TLS server receiving plaintext bytes, an HTTP/2 prior-knowledge endpoint, or a non-HTTP service — the handshake error surfaces through handle_error as this message.

Source

Thrown at packages/cli/src/serve/proxy.rs:59

                if start.elapsed() >= max_wait {
                    return Err(handle_error(anyhow::anyhow!(
                        "Backend not ready after {max_wait:?}: {e}"
                    )));
                }
                tracing::debug!("Backend not ready, retrying in {backoff:?}...");
                tokio::time::sleep(backoff).await;
                backoff = (backoff * 2).min(std::time::Duration::from_secs(2));
            }
        }
    };

    // Wrap the TCP stream for hyper
    let io = TokioIo::new(stream);

    // Perform HTTP/1.1 handshake on the same connection
    let (mut sender, conn) = http1::handshake(io)
        .await
        .map_err(|e| handle_error(anyhow::anyhow!("HTTP handshake failed: {e}")))?;

    // Spawn connection driver to keep it alive
    tokio::spawn(async move {
        if let Err(e) = conn.await {
            tracing::debug!("Connection closed: {e}");
        }
    });

    // Send request through the established connection (streaming body)
    sender
        .send_request(req)
        .await
        .map_err(|e| handle_error(anyhow::anyhow!("Request failed: {e}")))
}

/// Add routes to the router handling the specified proxy config.
///
/// We will proxy requests directed at either:

View on GitHub (pinned to 393d190a80)

Solutions

  1. Point the proxy at the backend's plain-HTTP listener; this code path only speaks HTTP/1.1 without TLS
  2. If the backend is HTTPS-only, terminate TLS locally (caddy/nginx/stunnel) and proxy to that local plain port
  3. Verify with curl http://host:port/ that the backend actually answers plaintext HTTP/1.1
  4. Check for port mix-ups between TLS (443/8443) and plain (80/8080) listeners

Example fix

// Dioxus.toml — before: TLS port addressed as plain HTTP
[web.proxy]
backend = "http://api.internal:443/api"

// after: terminate TLS locally and proxy plain HTTP
[web.proxy]
backend = "http://127.0.0.1:8080/api"
Defensive patterns

Strategy: validation

Validate before calling

# Confirm the backend speaks plaintext HTTP/1.1 before configuring the proxy
curl --http1.1 -fsS http://127.0.0.1:8080/api >/dev/null && echo ok || echo 'not a plain HTTP/1.1 endpoint'

Prevention

When it happens

Trigger: Configuring [web.proxy].backend with http:// against an HTTPS-only port (the TLS server rejects the plaintext handshake); proxying to a gRPC/HTTP-2 service; pointing the backend URL at a database or other raw TCP service.

Common situations: Using an https://-style URL but with the scheme dropped to http; reverse-proxy setups where TLS termination is expected on the dx side but none exists; port confusion between the TLS and plain listeners of the same backend.

Understand the failure class

Related errors


AI-assisted analysis of DioxusLabs/dioxus@393d190a80 (2026-08-16). Data as JSON: /api/errors/b9f68cc122c6128b. Report an issue: GitHub.