HTTP proxies can either relay a request at the HTTP layer or open an end-to-end TCP tunnel with CONNECT. Forcing the tunnel path is useful when a target behaves differently behind the proxy, when an application expects a direct socket to the origin, or when proxy policy troubleshooting needs proof of the exact handshake.
With a normal --proxy request to a plain HTTP URL, cURL sends the full URL to the proxy and the proxy fetches the resource on the client's behalf. Adding --proxytunnel changes that flow: cURL asks the HTTP proxy to open a raw TCP path to the target host and port first, then sends the request through that tunnel after the proxy returns 200 Connection established.
This option only applies when cURL is using an HTTP proxy. HTTPS targets already use CONNECT automatically with most HTTP proxies, so --proxytunnel matters mainly when forcing a tunnel for plain HTTP traffic or when proving whether the proxy allows CONNECT to a specific destination and port. Use -q while testing if ~/.curlrc or inherited proxy variables might otherwise hide the actual request path.
Steps to force proxy CONNECT tunneling with cURL:
- Send the request through the HTTP proxy without --proxytunnel first so the non-tunneled baseline is explicit.
$ curl -q -v --proxy http://egress-proxy.ops.example.test:8080 http://inventory-api.example.test/v1/health -o /dev/null * Trying 198.51.100.18:8080... * Connected to egress-proxy.ops.example.test (198.51.100.18) port 8080 > GET http://inventory-api.example.test/v1/health HTTP/1.1 > Host: inventory-api.example.test > User-Agent: curl/8.7.1 > Accept: */* > Proxy-Connection: Keep-Alive < HTTP/1.1 200 OK < Content-Type: application/json < Via: 1.1 egress-proxy.ops.example.test < X-Request-Id: req_01HV7KQW4X8PD9M2E3C6F1G5JN
Using -q ignores ~/.curlrc so the verbose trace reflects only the options shown here. In a normal plain-HTTP proxy flow, the proxy receives the full URL in the request line and no CONNECT handshake appears.
- Repeat the same request with --proxytunnel to force an HTTP CONNECT tunnel before the origin request is sent.
$ curl -q -v --proxy http://egress-proxy.ops.example.test:8080 --proxytunnel http://inventory-api.example.test/v1/health -o /dev/null * Trying 198.51.100.18:8080... * Connected to egress-proxy.ops.example.test (198.51.100.18) port 8080 * CONNECT tunnel: HTTP/1.1 negotiated * Establish HTTP proxy tunnel to inventory-api.example.test:80 > CONNECT inventory-api.example.test:80 HTTP/1.1 > Host: inventory-api.example.test:80 > User-Agent: curl/8.7.1 > Proxy-Connection: Keep-Alive < HTTP/1.1 200 Connection established * CONNECT phase completed * CONNECT tunnel established, response 200 > GET /v1/health HTTP/1.1 > Host: inventory-api.example.test < HTTP/1.1 200 OK < X-Request-Id: req_01HV7KQW4X8PD9M2E3C6F1G5JN
If the proxy returns anything other than 200 Connection established, the CONNECT request was not approved for that destination or port.
- Compare the same proxy against an HTTPS target so the automatic CONNECT behavior is clear.
$ curl -q -v --proxy http://egress-proxy.ops.example.test:8080 https://inventory-api.example.test/v1/health -o /dev/null * Trying 198.51.100.18:8080... * Connected to egress-proxy.ops.example.test (198.51.100.18) port 8080 * CONNECT tunnel: HTTP/1.1 negotiated * Establish HTTP proxy tunnel to inventory-api.example.test:443 > CONNECT inventory-api.example.test:443 HTTP/1.1 > Host: inventory-api.example.test:443 < HTTP/1.1 200 Connection established * CONNECT phase completed * ALPN: curl offers h2,http/1.1
HTTPS over an HTTP proxy already uses CONNECT in the common case, so --proxytunnel is mainly needed when a plain-HTTP request must be tunneled instead of relayed.
- Surface only the tunnel negotiation lines when a script or incident note needs simple proof that the tunnel was negotiated.
$ curl -q -sS -v --proxy http://egress-proxy.ops.example.test:8080 --proxytunnel http://inventory-api.example.test/v1/health -o /dev/null 2>&1 \\ | grep -E 'CONNECT|Connection established' * CONNECT tunnel: HTTP/1.1 negotiated > CONNECT inventory-api.example.test:80 HTTP/1.1 < HTTP/1.1 200 Connection established * CONNECT phase completed * CONNECT tunnel established, response 200
A successful tunnel is confirmed by the CONNECT request and the proxy's 200 Connection established response before the origin request begins.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
