aio-libs/aiohttp · error · ClientHttpProxyError

{reason}

Error message

{reason}

What it means

Raised by _create_proxy_connection() when the proxy's response to the CONNECT request is not HTTP 200. aiohttp builds the message from resp.reason (or the standard HTTPStatus phrase for the code if reason is missing) and raises ClientHttpProxyError, a ClientResponseError carrying status, headers, and history. Non-200 from CONNECT typically means 407 (auth required / failed) or 403/502 (proxy policy).

Source

Thrown at aiohttp/connector.py:1641

                # once the response is received and processed allowing
                # START_TLS to work on the connection below.
                protocol.set_response_params(
                    read_until_eof=True,
                    timeout_ceil_threshold=self._timeout_ceil_threshold,
                )
                resp = await proxy_resp.start(conn)
            except BaseException:
                proxy_resp.close()
                conn.close()
                raise
            else:
                conn._protocol = None
                try:
                    if resp.status != 200:
                        message = resp.reason
                        if message is None:
                            message = HTTPStatus(resp.status).phrase
                        raise ClientHttpProxyError(
                            proxy_resp.request_info,
                            resp.history,
                            status=resp.status,
                            message=message,
                            headers=resp.headers,
                        )
                except BaseException:
                    # It shouldn't be closed in `finally` because it's fed to
                    # `loop.start_tls()` and the docs say not to touch it after
                    # passing there.
                    transport.close()
                    raise

                return await self._start_tls_connection(
                    # Access the old transport for the last time before it's
                    # closed and forgotten forever:
                    transport,
                    req=req,

View on GitHub (pinned to c0ef574e29)

Solutions

  1. For 407: supply proxy auth via `aiohttp.BasicAuth` in the `proxy_headers=` argument or in the proxy URL (`http://user:pass@proxy:8080`).
  2. Confirm the target host/port is allowed by the proxy's ACL.
  3. Read `exc.status`, `exc.headers`, and the body for the proxy's reason text.
  4. Switch to a proxy that permits CONNECT to the destination, or use a direct connection.

Example fix

# before
async with session.get('https://target', proxy='http://proxy:8080') as r: ...
# 407 Proxy Authentication Required
# after
import aiohttp
auth = aiohttp.BasicAuth('user', 'pass')
async with session.get(
    'https://target',
    proxy='http://proxy:8080',
    proxy_headers={'Proxy-Authorization': auth.encode()},
) as r: ...
Defensive patterns

Strategy: try-catch

Validate before calling

import aiohttp

def proxy_auth_header(user: str, pw: str) -> dict:
    return {'Proxy-Authorization': aiohttp.BasicAuth(user, pw).encode()}

Try / catch

from aiohttp import ClientHttpProxyError
try:
    await session.get(url, proxy=proxy_url, proxy_headers=proxy_auth_header(u, p))
except ClientHttpProxyError as e:
    if e.status == 407:
        # refresh credentials and retry once
        ...
    raise

Prevention

When it happens

Trigger: Proxy returned 407 Proxy Authentication Required (bad/missing Proxy-Authorization), 403 Forbidden, 502/503, or any non-200 status to the CONNECT establishing the tunnel.

Common situations: Proxy credentials missing or wrong. Proxy allowlist excludes the target host. Proxy rate-limiting. Squid/forward-proxy returning 407 on first request because auth header wasn't supplied. HTTP proxy that refuses CONNECT to certain ports.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/c1e3ebfdb3842305.json. Report an issue: GitHub.