HTTP CONNECT Tunneling: How HTTPS Works Through Proxies

HTTP CONNECT Tunneling: How HTTPS Works Through Proxies

The HTTP CONNECT method is the mechanism that allows encrypted HTTPS traffic to flow through proxy servers. When your browser or application needs to access an HTTPS website through a proxy, it sends a CONNECT request asking the proxy to establish a raw TCP tunnel to the destination. Once the tunnel is established, the proxy simply relays bytes in both directions without inspecting or modifying the encrypted content.

The Problem CONNECT Solves

HTTP proxies can naturally handle HTTP traffic because they understand the protocol — they read the request, forward it, and relay the response. But HTTPS traffic is encrypted, creating a fundamental problem:

The Problem:

Client wants to access https://bank.com through a proxy.

If proxy tries to forward the request normally:
1. Client sends encrypted data to proxy
2. Proxy cannot read the data (encrypted with bank.com's certificate)
3. Proxy does not know what to do with it
4. Connection fails

The Solution — CONNECT creates a TCP tunnel:
1. Client tells proxy: "Connect me to bank.com:443"
2. Proxy opens TCP connection to bank.com:443
3. Proxy tells client: "Connection established"
4. Client performs TLS handshake directly with bank.com THROUGH the tunnel
5. Proxy just relays encrypted bytes back and forth

CONNECT Method Step by Step

The Full Flow

Client                      Proxy Server                    Target (bank.com:443)
  |                              |                               |
  |  1. CONNECT bank.com:443    |                               |
  |  HTTP/1.1                   |                               |
  |  Host: bank.com:443         |                               |
  |  Proxy-Authorization: ...   |                               |
  |----------------------------->                               |
  |                              |  2. TCP connect to            |
  |                              |     bank.com:443              |
  |                              |------------------------------->
  |                              |                               |
  |                              |  3. TCP connection             |
  |                              |     established               |
  |                              |<-------------------------------|
  |                              |                               |
  |  4. HTTP/1.1 200             |                               |
  |  Connection Established      |                               |
  |<-----------------------------|                               |
  |                                                              |
  |  5. TLS ClientHello ════════════════════════════════════════>|
  |  6. TLS ServerHello + Certificate <═════════════════════════|
  |  7. TLS Key Exchange ══════════════════════════════════════>|
  |  8. TLS Finished <═════════════════════════════════════════|
  |                                                              |
  |  ═══════════ Encrypted Application Data ═══════════════════|
  |  GET /account HTTP/1.1                                       |
  |  Cookie: session=abc123                                      |
  |  (proxy CANNOT read any of this)                             |

Raw HTTP Headers

-- Step 1: Client sends CONNECT request to proxy --

CONNECT bank.com:443 HTTP/1.1
Host: bank.com:443
Proxy-Authorization: Basic dXNlcjpwYXNz
Proxy-Connection: keep-alive
User-Agent: Mozilla/5.0

-- Step 4: Proxy responds with tunnel confirmation --

HTTP/1.1 200 Connection Established
Proxy-Agent: Squid/5.7

-- Steps 5-8: TLS handshake (binary data, not readable) --
-- After TLS: All data is encrypted end-to-end --

What the Proxy Can and Cannot See

What the Proxy Sees

✓ Destination hostname: bank.com
✓ Destination port: 443
✓ Your source IP address
✓ Amount of data transferred (bytes)
✓ Connection duration
✓ Connection timing (when established, when closed)

What the Proxy Cannot See

✗ URL path: /account/balance?id=12345
✗ Query parameters: ?search=something
✗ Request headers: Cookie, Authorization, User-Agent
✗ Request body: POST data, form submissions
✗ Response headers: Set-Cookie, Content-Type
✗ Response body: HTML, JSON, images, etc.
✗ Any application-layer data whatsoever

Implementation Examples

Python — How Requests Library Handles CONNECT

import requests

# When you make an HTTPS request through a proxy:
proxies = {"https": "http://user:pass@proxy.example.com:8080"}
response = requests.get("https://bank.com/account", proxies=proxies)

# Behind the scenes, the requests library:
# 1. Connects to proxy.example.com:8080 via TCP
# 2. Sends: CONNECT bank.com:443 HTTP/1.1
#           Proxy-Authorization: Basic dXNlcjpwYXNz
# 3. Receives: HTTP/1.1 200 Connection Established
# 4. Performs TLS handshake with bank.com through the tunnel
# 5. Sends the actual GET request encrypted within the tunnel
# 6. Receives encrypted response through the tunnel

Manual CONNECT with Sockets

import socket
import ssl
import base64

def connect_via_proxy(proxy_host, proxy_port, target_host, target_port,
                       proxy_user=None, proxy_pass=None):
    """Manually establish a CONNECT tunnel"""

    # Step 1: TCP connection to proxy
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((proxy_host, proxy_port))

    # Step 2: Send CONNECT request
    connect_request = f"CONNECT {target_host}:{target_port} HTTP/1.1\r\n"
    connect_request += f"Host: {target_host}:{target_port}\r\n"

    if proxy_user and proxy_pass:
        credentials = base64.b64encode(
            f"{proxy_user}:{proxy_pass}".encode()
        ).decode()
        connect_request += f"Proxy-Authorization: Basic {credentials}\r\n"

    connect_request += "\r\n"
    sock.sendall(connect_request.encode())

    # Step 3: Read proxy response
    response = b""
    while b"\r\n\r\n" not in response:
        response += sock.recv(4096)

    status_line = response.split(b"\r\n")[0].decode()
    if "200" not in status_line:
        raise Exception(f"CONNECT failed: {status_line}")

    print(f"Tunnel established: {status_line}")

    # Step 4: Wrap with TLS
    context = ssl.create_default_context()
    tls_sock = context.wrap_socket(sock, server_hostname=target_host)

    # Step 5: Send HTTP request through encrypted tunnel
    request = f"GET / HTTP/1.1\r\nHost: {target_host}\r\n\r\n"
    tls_sock.sendall(request.encode())

    # Step 6: Read response
    data = tls_sock.recv(4096)
    print(data.decode())

    tls_sock.close()

# Usage
connect_via_proxy(
    proxy_host="proxy.example.com",
    proxy_port=8080,
    target_host="httpbin.org",
    target_port=443,
    proxy_user="user",
    proxy_pass="pass"
)

Node.js — Manual CONNECT

const http = require('http');
const tls = require('tls');

function connectViaProxy(proxyHost, proxyPort, targetHost, targetPort) {
    return new Promise((resolve, reject) => {
        const req = http.request({
            host: proxyHost,
            port: proxyPort,
            method: 'CONNECT',
            path: `${targetHost}:${targetPort}`,
            headers: {
                'Proxy-Authorization': 'Basic ' +
                    Buffer.from('user:pass').toString('base64')
            }
        });

        req.on('connect', (res, socket) => {
            if (res.statusCode !== 200) {
                reject(new Error(`CONNECT failed: ${res.statusCode}`));
                return;
            }

            // Wrap with TLS
            const tlsSocket = tls.connect({
                socket: socket,
                servername: targetHost
            }, () => {
                // Send request through encrypted tunnel
                tlsSocket.write(
                    `GET / HTTP/1.1\r\nHost: ${targetHost}\r\n\r\n`
                );
            });

            tlsSocket.on('data', (data) => {
                resolve(data.toString());
                tlsSocket.end();
            });
        });

        req.end();
    });
}

CONNECT and Proxy Authentication

Authentication happens during the CONNECT phase, before the tunnel is established:

Authentication Flow:

Attempt 1 (no credentials):
  Client → CONNECT target:443 HTTP/1.1
  Proxy  → 407 Proxy Authentication Required
           Proxy-Authenticate: Basic realm="Proxy"

Attempt 2 (with credentials):
  Client → CONNECT target:443 HTTP/1.1
           Proxy-Authorization: Basic dXNlcjpwYXNz
  Proxy  → 200 Connection Established

Important: Authentication is in plaintext (Base64 encoded)
           unless the connection to the proxy itself uses TLS

Error Handling

Common CONNECT errors and their meanings:

Status CodeMeaningSolution
200Connection EstablishedSuccess — proceed with TLS
403ForbiddenProxy blocks this destination or your IP
407Auth RequiredAdd/fix Proxy-Authorization header
502Bad GatewayProxy cannot reach target server
503Service UnavailableProxy is overloaded
504Gateway TimeoutTarget server did not respond in time
def handle_connect_errors(response_line):
    status_code = int(response_line.split()[1])

    errors = {
        403: "Proxy forbids this connection. Check ACLs or destination whitelist.",
        407: "Authentication required. Provide Proxy-Authorization header.",
        502: "Proxy cannot reach target. Check target host:port.",
        503: "Proxy overloaded. Retry after delay.",
        504: "Target timed out. Increase timeout or check target availability."
    }

    if status_code != 200:
        raise Exception(errors.get(status_code, f"Unknown error: {status_code}"))

Security Implications

Why CONNECT Is Secure

Security guarantees of CONNECT tunneling:

1. End-to-end encryption: TLS between client and target
   - Proxy cannot decrypt (does not have target's private key)
   - No man-in-the-middle possible (certificate validation)

2. Content integrity: TLS ensures data is not modified
   - Proxy cannot inject content
   - Proxy cannot modify headers
   - Any tampering breaks TLS and triggers errors

3. Authentication isolation:
   - Proxy auth (Basic) is separate from target auth
   - Target auth happens inside encrypted tunnel
   - Proxy never sees target credentials

When CONNECT Can Be Attacked

Attack: SSL/TLS Interception (SSL Bumping)

Corporate proxy with custom CA certificate:
1. Client sends CONNECT
2. Proxy accepts, but generates its own certificate for target
3. Client's TLS connects to PROXY (not target)
4. Proxy's TLS connects to TARGET
5. Proxy decrypts, inspects, re-encrypts

Detection:
- Certificate issuer shows proxy CA, not target's real CA
- Certificate pinning fails
- Some browsers show warning

This only works when proxy's CA is trusted by client OS
(corporate environments install custom CA certificates)

Frequently Asked Questions

Does every HTTP proxy support the CONNECT method?

Most modern HTTP proxies support CONNECT, but some restrict which ports are allowed. Many proxies only permit CONNECT to port 443 (HTTPS) and block other ports to prevent abuse (like using CONNECT to tunnel non-HTTPS traffic). Check your proxy provider’s documentation for port restrictions.

Is the CONNECT request itself encrypted?

No. The CONNECT request (including the destination hostname and any proxy authentication) is sent in plaintext. Only the traffic after the tunnel is established is encrypted by TLS. To encrypt the CONNECT request itself, you need a TLS connection to the proxy (connecting to the proxy over HTTPS).

Can CONNECT be used for non-HTTPS traffic?

Technically yes — CONNECT creates a raw TCP tunnel that can carry any protocol. However, most proxy servers restrict CONNECT to port 443 for security reasons. If allowed, you could tunnel SSH (port 22), SMTP (port 587), or any other TCP protocol through CONNECT.

Why does my proxy URL start with http:// even for HTTPS targets?

The http:// in the proxy URL refers to the protocol used to communicate with the proxy server — not the target. You connect to the proxy using HTTP (to send the CONNECT request), then establish an encrypted tunnel to the HTTPS target through the proxy. This is standard and correct behavior.

How does CONNECT affect proxy performance?

CONNECT tunnels add minimal overhead. The proxy only handles the initial handshake, then simply relays TCP packets in both directions without inspection. This is actually less work than HTTP proxying (which requires header parsing), making CONNECT tunnels slightly more efficient for high-throughput transfers. For more on proxy performance, see our how proxy rotation works guide.

Conclusion

HTTP CONNECT is the essential mechanism that enables secure HTTPS traffic through proxy servers. By creating a raw TCP tunnel, it allows end-to-end TLS encryption while giving the proxy only minimal metadata (destination hostname and port). Understanding CONNECT is fundamental to working with any proxy infrastructure that handles modern web traffic.

For related topics, see our guides on HTTP vs HTTPS proxies and proxy authentication methods.


Related Reading

Scroll to Top