aio-libs/aiohttp · error · TypeError

ssl should be SSLContext, Fingerprint, or bool, got {ssl!r}

Error message

ssl should be SSLContext, Fingerprint, or bool, got {ssl!r} instead.

What it means

Raised by TCPConnector.__init__ when the `ssl` argument is not one of SSLContext, Fingerprint, or bool (the tuple SSL_ALLOWED_TYPES). aiohttp checks at construction so that an invalid ssl policy fails fast rather than producing confusing handshake errors later. Note: on builds compiled without ssl, only bool is allowed.

Source

Thrown at aiohttp/connector.py:1025

        limit_per_host: int = 0,
        enable_cleanup_closed: bool = False,
        timeout_ceil_threshold: float = 5,
        happy_eyeballs_delay: float | None = 0.25,
        interleave: int | None = None,
        socket_factory: SocketFactoryType | None = None,
        ssl_shutdown_timeout: _SENTINEL | None | float = sentinel,
    ):
        super().__init__(
            keepalive_timeout=keepalive_timeout,
            force_close=force_close,
            limit=limit,
            limit_per_host=limit_per_host,
            enable_cleanup_closed=enable_cleanup_closed,
            timeout_ceil_threshold=timeout_ceil_threshold,
        )

        if not isinstance(ssl, SSL_ALLOWED_TYPES):
            raise TypeError(
                "ssl should be SSLContext, Fingerprint, or bool, "
                f"got {ssl!r} instead."
            )
        self._ssl = ssl

        self._resolver: AbstractResolver
        if resolver is None:
            self._resolver = DefaultResolver()
            self._resolver_owner = True
        else:
            self._resolver = resolver
            self._resolver_owner = False

        self._use_dns_cache = use_dns_cache
        self._cached_hosts = _DNSCacheTable(
            ttl=ttl_dns_cache, max_size=dns_cache_max_size
        )
        self._throttle_dns_futures: dict[tuple[str, int], set[asyncio.Future[None]]] = (

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Pass a bool: `TCPConnector(ssl=False)` to disable verification, `ssl=True` to use a verified default context.
  2. Pass an `ssl.SSLContext` you configured, or an `aiohttp.Fingerprint` for pinning.
  3. Coerce env-var strings: `ssl=(os.environ['VERIFY'] == 'true')` rather than passing the string.

Example fix

# before
connector = aiohttp.TCPConnector(ssl='false')
# after
connector = aiohttp.TCPConnector(ssl=False)
Defensive patterns

Strategy: type-guard

Validate before calling

import ssl
from aiohttp import Fingerprint

def coerce_ssl(v):
    if isinstance(v, (ssl.SSLContext, Fingerprint, bool)):
        return v
    if isinstance(v, int):
        return bool(v)
    if isinstance(v, str):
        return v.lower() == 'true'
    raise TypeError(f'invalid ssl value: {v!r}')

Type guard

import ssl
from aiohttp import Fingerprint

def is_valid_ssl(v) -> bool:
    return isinstance(v, (ssl.SSLContext, Fingerprint, bool))

Try / catch

try:
    connector = aiohttp.TCPConnector(ssl=ssl_value)
except TypeError as e:
    if 'ssl should be' in str(e):
        connector = aiohttp.TCPConnector(ssl=coerce_ssl(ssl_value))
    raise

Prevention

When it happens

Trigger: Passing `TCPConnector(ssl='true')`, `ssl=1` (int, not bool), `ssl=None` to TCPConnector (None is not in the allowed tuple), or `ssl={'verify': False}`.

Common situations: String 'true'/'false' from an env var passed verbatim. Integer 0/1 instead of bool. Confusing `verify_ssl`/`fingerprint` kwargs (which go on the request) with the connector-level `ssl`. Passing None expecting 'use defaults' (use True or an SSLContext instead).

Related errors


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