aio-libs/aiohttp · error · ClientConnectorCertificateError

Cannot connect to host

Error message

Cannot connect to host {host}:{port} ssl:{ssl} [{certificate_error.__class__.__name__}: {certificate_error.args}]

What it means

Raised by _wrap_create_connection during a direct (non-proxy) TCP+TLS connect when the SSL handshake raises a certificate error (ssl.CertificateError or one of the cert_errors subclasses). aiohttp wraps the underlying failure in ClientConnectorCertificateError so callers can distinguish certificate problems from generic connection errors. The message echoes the original exception class and args so the CA/hostname/expiry cause is visible.

Solutions

  1. Build an ssl.SSLContext with load_verify_locations(cafile=...) pointing at the correct CA and pass it to the connector/session.
  2. Refresh the system CA bundle (install ca-certificates, run update-ca-certificates).
  3. Verify system clock (NTP) so cert validity windows are evaluated correctly.
  4. For diagnostics only, ssl=False disables verification - never use in production.

Example fix

# before
await session.get('https://internal.corp/')
# after
import ssl
ctx = ssl.create_default_context(cafile='/etc/ssl/internal-ca.pem')
connector = aiohttp.TCPConnector(ssl=ctx)
async with aiohttp.ClientSession(connector=connector) as s:
    await s.get('https://internal.corp/')
Defensive patterns

Strategy: try-catch

Validate before calling

import ssl

def trusted_context(cafile):
    ctx = ssl.create_default_context(cafile=cafile)
    return ctx

# preflight: ensure the host's cert chains to the supplied CA before trusting it
ctx = trusted_context('/etc/ssl/internal-ca.pem')

Type guard

null

Try / catch

try:
    resp = await session.get(url)
except aiohttp.ClientConnectorCertificateError as exc:
    log.error('cert failure for %s: %s', exc.host, exc.certificate_error)
    # surface to operator; do not silently disable verification
    raise

Prevention

When it happens

Trigger: Server presents a self-signed or expired certificate; hostname on the cert does not match req.url.host; missing intermediate certificate; client's CA bundle does not trust the issuer; client clock skewed so a valid cert appears expired/not-yet-valid.

Common situations: Hitting internal services with private CAs without installing the CA; staging environments with self-signed certs; containers missing ca-certificates; system time wrong in VMs; certificate renewal lapsed.

Understand the failure class

Related errors


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

Appendix: source

Thrown at aiohttp/connector.py:1347

            ):
                sock = await aiohappyeyeballs.start_connection(
                    addr_infos=addr_infos,
                    local_addr_infos=self._local_addr_infos,
                    happy_eyeballs_delay=self._happy_eyeballs_delay,
                    interleave=self._interleave,
                    loop=self._loop,
                    socket_factory=self._socket_factory,
                )
                # Add ssl_shutdown_timeout for Python 3.11+ when SSL is used
                if (
                    kwargs.get("ssl")
                    and self._ssl_shutdown_timeout
                    and sys.version_info >= (3, 11)
                ):
                    kwargs["ssl_shutdown_timeout"] = self._ssl_shutdown_timeout
                return await create_connection(self._loop, *args, **kwargs, sock=sock)
        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

    def _warn_about_tls_in_tls(
        self,
        underlying_transport: asyncio.Transport,
        req: ClientRequest,
    ) -> None:
        """Issue a warning if the requested URL has HTTPS scheme."""
        if req.url.scheme != "https":
            return

        # TLS-in-TLS only applies when the proxy itself is HTTPS.
        # When the proxy is HTTP, start_tls upgrades a plain TCP connection,

View on GitHub (pinned to d041d4d0fd)