{"record":{"id":"b9f68cc122c6128b","repo":"DioxusLabs/dioxus","slug":"http-handshake-failed-e","errorCode":null,"errorMessage":"HTTP handshake failed: {e}","messagePattern":"HTTP handshake failed: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":500,"severity":"error","filePath":"packages/cli/src/serve/proxy.rs","lineNumber":59,"sourceCode":"                if start.elapsed() >= max_wait {\n                    return Err(handle_error(anyhow::anyhow!(\n                        \"Backend not ready after {max_wait:?}: {e}\"\n                    )));\n                }\n                tracing::debug!(\"Backend not ready, retrying in {backoff:?}...\");\n                tokio::time::sleep(backoff).await;\n                backoff = (backoff * 2).min(std::time::Duration::from_secs(2));\n            }\n        }\n    };\n\n    // Wrap the TCP stream for hyper\n    let io = TokioIo::new(stream);\n\n    // Perform HTTP/1.1 handshake on the same connection\n    let (mut sender, conn) = http1::handshake(io)\n        .await\n        .map_err(|e| handle_error(anyhow::anyhow!(\"HTTP handshake failed: {e}\")))?;\n\n    // Spawn connection driver to keep it alive\n    tokio::spawn(async move {\n        if let Err(e) = conn.await {\n            tracing::debug!(\"Connection closed: {e}\");\n        }\n    });\n\n    // Send request through the established connection (streaming body)\n    sender\n        .send_request(req)\n        .await\n        .map_err(|e| handle_error(anyhow::anyhow!(\"Request failed: {e}\")))\n}\n\n/// Add routes to the router handling the specified proxy config.\n///\n/// We will proxy requests directed at either:","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/DioxusLabs/dioxus/blob/393d190a801ccb441d41923e232289b4f8a5c669/packages/cli/src/serve/proxy.rs#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Point the proxy at the backend's plain-HTTP listener; this code path only speaks HTTP/1.1 without TLS","If the backend is HTTPS-only, terminate TLS locally (caddy/nginx/stunnel) and proxy to that local plain port","Verify with curl http://host:port/ that the backend actually answers plaintext HTTP/1.1","Check for port mix-ups between TLS (443/8443) and plain (80/8080) listeners"],"exampleFix":"// Dioxus.toml — before: TLS port addressed as plain HTTP\n[web.proxy]\nbackend = \"http://api.internal:443/api\"\n\n// after: terminate TLS locally and proxy plain HTTP\n[web.proxy]\nbackend = \"http://127.0.0.1:8080/api\"","handlingStrategy":"validation","validationCode":"# Confirm the backend speaks plaintext HTTP/1.1 before configuring the proxy\ncurl --http1.1 -fsS http://127.0.0.1:8080/api >/dev/null && echo ok || echo 'not a plain HTTP/1.1 endpoint'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["The dx proxy speaks plaintext HTTP/1.1 only — terminate TLS at or before the backend","Double-check scheme and port when copying backend URLs","Test with curl first whenever the handshake error appears"],"tags":["cli","dev-server","proxy","http","tls"],"backgroundTag":null,"analyzedSha":"393d190a801ccb441d41923e232289b4f8a5c669","analyzedAt":"2026-08-16T11:27:45.815Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}