windmill-labs/windmill · error

HTTP proxy closed connection before sending CONNECT response

Error message

HTTP proxy closed connection before sending CONNECT response

What it means

`http_connect_tunnel` opens a plain TCP connection to the HTTP proxy, sends a `CONNECT host:port` request, then reads the status line of the proxy's response. If `read_line` returns `n == 0`, the proxy closed the TCP connection before answering the CONNECT request, so no tunnel can be established and the function converts this into `io::ErrorKind::UnexpectedEof`.

Source

Thrown at backend/windmill-trigger-websocket/src/proxy.rs:272

    let mut stream = TcpStream::connect((proxy.host.as_str(), proxy.port)).await?;

    let host_header = format!("{}:{}", target_host, target_port);
    let mut req = format!("CONNECT {h} HTTP/1.1\r\nHost: {h}\r\n", h = host_header,);
    if let Some(ref auth) = proxy.basic_auth {
        req.push_str("Proxy-Authorization: Basic ");
        req.push_str(auth);
        req.push_str("\r\n");
    }
    req.push_str("Proxy-Connection: keep-alive\r\n\r\n");

    stream.write_all(req.as_bytes()).await?;
    stream.flush().await?;

    let mut reader = BufReader::new(stream);
    let mut status_line = String::new();
    let n = reader.read_line(&mut status_line).await?;
    if n == 0 {
        return Err(io::Error::new(
            io::ErrorKind::UnexpectedEof,
            "HTTP proxy closed connection before sending CONNECT response",
        ));
    }

    let status_ok = status_line
        .split_whitespace()
        .nth(1)
        .map(|s| s == "200")
        .unwrap_or(false);

    loop {
        let mut line = String::new();
        let n = reader.read_line(&mut line).await?;
        if n == 0 || line == "\r\n" || line == "\n" {
            break;
        }
    }

View on GitHub (pinned to e474e8803c)

Solutions

  1. Confirm the proxy speaks plain HTTP CONNECT on that port (e.g. `curl -x http://proxy:port https://target -v`); if it needs TLS, fix the scheme
  2. Check proxy ACLs/logs for denied CONNECT to the target host:port
  3. Verify the target host:port is allowed by the proxy's whitelist/allowlist config
  4. Test connectivity to the proxy itself (`nc -vz proxy port`) and check the proxy service logs for crashes or timeouts

Example fix

// before
// client config: proxy = "http://proxy.corp:3128" but proxy requires TLS
// after
// proxy = "https://proxy.corp:3128" (or configure the proxy to accept plain CONNECT)
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check the proxy speaks plain CONNECT before use
let mut s = tokio::net::TcpStream::connect((proxy_host, proxy_port)).await?;
s.write_all(b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n").await?;
let mut buf = vec![0u8; 16];
let n = s.read(&mut buf).await?;
if n == 0 { return Err(anyhow!("proxy closes connection on CONNECT - check proxy ACLs/TLS mode")); }

Try / catch

match connect_async_with_proxy(&url, &proxy).await {
    Err(WsError::Io(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
        eprintln!("proxy dropped the CONNECT request: {}", e);
        // surface a config hint: wrong scheme (TLS vs plain), ACL denial, proxy down
    }
    Err(e) => return Err(e.into()),
    Ok(_) => {}
}

Prevention

When it happens

Trigger: Calling `connect_async_with_proxy` (via `test_connection` or `get_consumer`) where the configured HTTP proxy accepts the TCP connection but closes it immediately upon (or before) responding to the CONNECT request — e.g. proxy rejects the target, times out, or requires TLS on the proxy link.

Common situations: Proxy expects HTTPS (TLS) on the client link but gets plaintext CONNECT; proxy software (squid, mitmproxy, Istio sidecar) has an ACL denying the destination; idle/connection limits; proxy crashed between accept and respond.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/671db890a5a0b35f. Report an issue: GitHub.