aio-libs/aiohttp · error · ClientConnectionError

Cannot initialize a TLS-in-TLS connection to host

Error message

Cannot initialize a TLS-in-TLS connection to host {req.url.host!s}:{req.url.port:d} through an underlying connection to an HTTPS proxy {req.proxy!s} ssl:{req.ssl or 'default'} [{type_err!s}]

What it means

Raised from _start_tls_connection when start_tls() raises TypeError - historically because the underlying asyncio transport does not support start_tls (the stdlib asyncio SSL transport could not be re-upgraded before Python 3.11, bpo-44011). aiohttp catches the TypeError and re-raises it as ClientConnectionError with the explicit 'Cannot initialize a TLS-in-TLS connection' message so the cause is obvious. It is the hard-failure counterpart of the runtime warning emitted by _warn_about_tls_in_tls.

Solutions

  1. Upgrade to Python 3.11+ where asyncio natively supports TLS-in-TLS.
  2. Switch to uvloop (or aiofastnet if available) - their transports advertise start_tls compatibility.
  3. Reconfigure egress to use an HTTP (CONNECT) proxy so only one TLS layer is needed.
  4. Avoid chaining an HTTPS target through an HTTPS proxy if the runtime cannot satisfy TLS-in-TLS.

Example fix

# before - Python 3.10, default asyncio
await session.get('https://target/', proxy='https://corp-proxy:443')
# after - use an HTTP proxy instead
await session.get('https://target/', proxy='http://corp-proxy:8080')
# or upgrade to Python 3.11+ / install uvloop
Defensive patterns

Strategy: fallback

Validate before calling

import sys

def can_tls_in_tls() -> bool:
    return sys.version_info >= (3, 11)

if not can_tls_in_tls() and proxy_url.startswith('https://'):
    raise RuntimeError('use an HTTP proxy or upgrade to Python 3.11+ for HTTPS-over-HTTPS')

Type guard

null

Try / catch

try:
    resp = await session.get(url, proxy=proxy_url)
except aiohttp.ClientConnectionError as exc:
    if 'TLS-in-TLS' in str(exc):
        # fallback to an HTTP proxy (single TLS layer)
        http_proxy = proxy_url.replace('https://', 'http://')
        resp = await session.get(url, proxy=http_proxy)
    else:
        raise

Prevention

When it happens

Trigger: HTTPS request through an HTTPS proxy on Python < 3.11 with the default asyncio loop; using a custom event loop whose transport lacks start_tls support; uvloop/aiofastnet absent so the stdlib limitation applies.

Common situations: Legacy Python runtime forced to do TLS-in-TLS; corporate HTTPS-proxy-only egress on an older interpreter; event loop without start_tls-compatible transports.

Understand the failure class

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/733b0597730e40df. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/connector.py:1480

                        except ServerFingerprintMismatch:
                            tls_transport.close()
                            if not self._cleanup_closed_disabled:
                                self._cleanup_closed_transports.append(tls_transport)
                            raise
        except cert_errors as exc:
            raise ClientConnectorCertificateError(req.connection_key, exc) from exc
        except ssl_errors as exc:
            raise ClientConnectorSSLError(req.connection_key, exc) from exc
        except OSError as exc:
            if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
                raise
            raise client_error(req.connection_key, exc) from exc
        except TypeError as type_err:
            # Example cause looks like this:
            # TypeError: transport <asyncio.sslproto._SSLProtocolTransport
            # object at 0x7f760615e460> is not supported by start_tls()

            raise ClientConnectionError(
                "Cannot initialize a TLS-in-TLS connection to host "
                f"{req.url.host!s}:{req.url.port:d} through an underlying connection "
                f"to an HTTPS proxy {req.proxy!s} ssl:{req.ssl or 'default'} "
                f"[{type_err!s}]"
            ) from type_err
        else:
            if tls_transport is None:
                msg = "Failed to start TLS (possibly caused by closing transport)"
                raise client_error(req.connection_key, OSError(msg))
            tls_proto.connection_made(
                tls_transport
            )  # Kick the state machine of the new TLS protocol

        return tls_transport, tls_proto

    def _convert_hosts_to_addr_infos(
        self, hosts: list[ResolveResult]
    ) -> list[AddrInfoType]:

View on GitHub (pinned to d041d4d0fd)