Skip to content

HttpClient: PUT/POST uploads stall and time out through HTTPS CONNECT proxy tunnel #126858

@savitha-qs

Description

@savitha-qs

Description

HTTP uploads (PUT) through an HTTPS forward proxy via CONNECT tunnel stall after sending ~17 KB, eventually timing out after 75 seconds. The server then returns HTTP 400. HTTP downloads (GET) through the same CONNECT tunnel work correctly at full speed.

This was observed with azsdk-net-Storage.Blobs/12.27.0 uploading to Azure Blob Storage, but the Azure SDK team confirmed they use the standard .NET proxy and HTTP stack with no custom logic (Azure/azure-sdk-for-net), so the issue is in the runtime's HTTP handling.

Environment

  • Runtime: .NET 8.0.25
  • OS: Ubuntu 24.04.4 LTS (x64)
  • Application: GitHub Actions runner (GitHubActionsRunner-linux-x64/2.333.1) using actions/upload-artifact@v4
  • Proxy: GCP Secure Web Proxy (HTTPS forward proxy, CONNECT tunnel, no TLS inspection/interception)
  • Proxy config: HTTPS_PROXY=http://<proxy-ip>:443

Observed behavior

The proxy successfully establishes the CONNECT tunnel (proxy logs confirm HTTP 200 for the CONNECT request and a completed TLS handshake). Data then flows through the encrypted tunnel, but the upload stalls almost immediately.

Failed upload (PUT to Azure Blob via .NET HttpClient):

CONNECT productionresultssa6.blob.core.windows.net:443 → status=200 (tunnel established)
  user-agent: azsdk-net-Storage.Blobs/12.27.0 (.NET 8.0.25; Ubuntu 24.04.4 LTS)
  requestSize=17259 bytes   (only ~17 KB sent in 75 seconds — upload stalled)
  responseSize=8828 bytes   (error response from server)
  latency=74.999634s        (timed out)

Successful downloads (GET from Azure Blob via Node.js — same proxy, same CONNECT tunnel):

CONNECT productionresultssa11.blob.core.windows.net:443 → status=200
  requestSize=1767 bytes
  responseSize=4207083 bytes   (~4 MB downloaded successfully)
  latency=~1s

The proxy logs these metrics at the tunnel level (outer CONNECT). The proxy cannot see the inner HTTP method or status since the tunnel is end-to-end encrypted. The "Bad Request" (400) error is reported by the application (actions/upload-artifact) and originates from the Azure Blob server inside the tunnel — likely because it received an incomplete or malformed upload due to the stall.

Expected behavior

Uploads through a CONNECT tunnel should complete successfully, the same way downloads do. The CONNECT tunnel is a transparent TCP pipe after establishment — the proxy does not inspect, modify, or interfere with the encrypted traffic.

Reproduction Steps

  1. Configure an HTTPS forward proxy that supports CONNECT tunnels (e.g., GCP Secure Web Proxy, Squid with ssl_bump none).
  2. Set HTTPS_PROXY=http://<proxy-ip>:<port> in the environment.
  3. Use HttpClient (via SocketsHttpHandler) to perform a PUT upload to any HTTPS endpoint through the proxy.
  4. Compare with a GET download to the same endpoint through the same proxy.
  5. Observe: downloads complete normally, uploads stall and time out.

A minimal repro using HttpClient directly (without the Azure SDK) would isolate whether the issue is in SocketsHttpHandler's proxy tunnel handling for request bodies.

Workaround

Set NO_PROXY=.blob.core.windows.net (leading-dot syntax — the *.example.com wildcard syntax does not work reliably in .NET) to bypass the proxy for the affected endpoints.

Note: the leading-dot vs wildcard syntax difference for NO_PROXY in .NET is a separate known issue that compounds this problem for users trying to configure proxy exceptions.

Possible areas to investigate

  • SocketsHttpHandler CONNECT tunnel establishment and subsequent request body streaming
  • Expect: 100-continue handling through proxy tunnels — if the proxy or tunnel setup delays the 100 Continue response, the client may stall waiting before sending the request body
  • HTTP/2 negotiation through CONNECT tunnels — if the client negotiates HTTP/2 with the origin server through the tunnel but the framing interacts poorly with the tunnel's TCP stream
  • Chunked transfer encoding or content-length handling differences between GET and PUT code paths when proxied

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.Net.Httpneeds-author-actionAn issue or pull request that requires more info or actions from the author.untriagedNew issue has not been triaged by the area owner

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions