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
- Configure an HTTPS forward proxy that supports CONNECT tunnels (e.g., GCP Secure Web Proxy, Squid with
ssl_bump none).
- Set
HTTPS_PROXY=http://<proxy-ip>:<port> in the environment.
- Use
HttpClient (via SocketsHttpHandler) to perform a PUT upload to any HTTPS endpoint through the proxy.
- Compare with a GET download to the same endpoint through the same proxy.
- 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
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.0uploading 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
GitHubActionsRunner-linux-x64/2.333.1) usingactions/upload-artifact@v4HTTPS_PROXY=http://<proxy-ip>:443Observed 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):
Successful downloads (GET from Azure Blob via Node.js — same proxy, same CONNECT tunnel):
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
ssl_bump none).HTTPS_PROXY=http://<proxy-ip>:<port>in the environment.HttpClient(viaSocketsHttpHandler) to perform a PUT upload to any HTTPS endpoint through the proxy.A minimal repro using
HttpClientdirectly (without the Azure SDK) would isolate whether the issue is inSocketsHttpHandler's proxy tunnel handling for request bodies.Workaround
Set
NO_PROXY=.blob.core.windows.net(leading-dot syntax — the*.example.comwildcard 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_PROXYin .NET is a separate known issue that compounds this problem for users trying to configure proxy exceptions.Possible areas to investigate
SocketsHttpHandlerCONNECT tunnel establishment and subsequent request body streamingExpect: 100-continuehandling through proxy tunnels — if the proxy or tunnel setup delays the 100 Continue response, the client may stall waiting before sending the request body