DioxusLabs/dioxus · error · anyhow::Error
Backend not ready after {max_wait:?}: {e}
Error message
Backend not ready after {max_wait:?}: {e} What it means
When [web.proxy] is configured, the dx dev server forwards matching requests to the backend via send_with_retry: it retries TCP connect with exponential backoff (100ms doubling, capped at 2s) for up to 30 seconds. If the backend still refuses connections after max_wait, the last io error is wrapped into this message and converted through handle_error into an error response for the browser.
Source
Thrown at packages/cli/src/serve/proxy.rs:42
url: &Uri,
req: Request<MyBody>,
handle_error: fn(Error) -> Response<Body>,
) -> std::result::Result<Response<hyper::body::Incoming>, Response<Body>> {
let host = url.host().unwrap_or("127.0.0.1");
let port = url.port_u16().unwrap_or(80);
let addr = format!("{host}:{port}");
let mut backoff = std::time::Duration::from_millis(100);
let max_wait = std::time::Duration::from_secs(30);
let start = std::time::Instant::now();
// Retry TCP connect until backend is ready
let stream = loop {
match TcpStream::connect(&addr).await {
Ok(stream) => break stream,
Err(e) => {
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}")))?;
View on GitHub (pinned to 393d190a80)
Solutions
- Start your backend and confirm it listens on the exact host:port from [web.proxy].backend
- Fix the URL in Dioxus.toml (scheme + port) to match the running backend, e.g. backend = "http://127.0.0.1:8080/api"
- Verify liveness independently: curl http://127.0.0.1:<port>/ from the same machine
- If the backend legitimately needs more than 30s to boot, delay loading proxied pages or request a configurable timeout upstream
Example fix
// Dioxus.toml — before: backend on 3000, but the API runs on 8080 [web.proxy] backend = "http://127.0.0.1:3000/api" // after [web.proxy] backend = "http://127.0.0.1:8080/api"
Defensive patterns
Strategy: retry
Validate before calling
# Prove the backend is up before dx serve (or before opening the page) for i in $(seq 1 50); do curl -fsf http://127.0.0.1:8080/api >/dev/null && break; done; curl -fsf http://127.0.0.1:8080/api >/dev/null || echo 'backend down — fix before proxying'
Prevention
- Start the backend before dx serve, or use a process manager that boots both
- Keep [web.proxy].backend host:port in lockstep with the backend's configured listener
- Add a /health endpoint to the backend and probe it in dev scripts
When it happens
Trigger: dx serve with a [web.proxy].backend that nothing is listening on for 30+ seconds — backend not started, crashed during startup, wrong host/port in Dioxus.toml, or container networking pointing at the wrong address.
Common situations: Forgetting to start the API server alongside dx serve; port mismatch after changing the backend's port; backend still compiling/booting when the first proxied request arrives; Docker service names not resolvable from the host.
Related errors
- Request failed: {e}
- HTTP handshake failed: {e}
- Failed to create new router after hot-patch!
- Syntax Error in line {line} column {column}: {err}
- Package type '{invalid:?}' is not supported for bundle forma
AI-assisted analysis of DioxusLabs/dioxus@393d190a80 (2026-08-16).
Data as JSON: /api/errors/8e6b69d790cdc8d3.
Report an issue: GitHub.