python/cpython · error · TypeError

ssl argument must be an SSLContext or None

Error message

ssl argument must be an SSLContext or None

What it means

Raised by create_server() when the ssl argument is a bool (True/False). Historically ssl=True was interpreted as a default context; that footgun was removed, so ssl must be an ssl.SSLContext or None — True does not mean 'default context' anymore.

Source

Thrown at Lib/asyncio/base_events.py:1572

            ssl_shutdown_timeout=None,
            start_serving=True):
        """Create a TCP server.

        The host parameter can be a string, in that case the TCP server is
        bound to host and port.

        The host parameter can also be a sequence of strings and in that
        case the TCP server is bound to all hosts of the sequence.  If
        a host appears multiple times (possibly indirectly e.g. when
        hostnames resolve to the same IP address), the server is only bound
        once to that host.

        Return a Server object which can be used to stop the service.

        This method is a coroutine.
        """
        if isinstance(ssl, bool):
            raise TypeError('ssl argument must be an SSLContext or None')

        if ssl_handshake_timeout is not None and ssl is None:
            raise ValueError(
                'ssl_handshake_timeout is only meaningful with ssl')

        if ssl_shutdown_timeout is not None and ssl is None:
            raise ValueError(
                'ssl_shutdown_timeout is only meaningful with ssl')

        if sock is not None:
            _check_ssl_socket(sock)

        if host is not None or port is not None:
            if sock is not None:
                raise ValueError(
                    'host/port and sock can not be specified at the same time')

            if reuse_address is None:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Pass ssl=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) (plus load_cert_chain) for a TLS server
  2. Pass ssl=None (or omit it) for plaintext
  3. If a bool flag drives TLS in your config, branch on it and select the context yourself

Example fix

# before
server = await loop.create_server(factory, '0.0.0.0', 443, ssl=True)

# after
ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ctx.load_cert_chain('cert.pem', 'key.pem')
server = await loop.create_server(factory, '0.0.0.0', 443, ssl=ctx)
Defensive patterns

Strategy: type-guard

Validate before calling

import ssl
assert ssl is None or isinstance(ssl, ssl.SSLContext), 'ssl must be SSLContext or None'

Type guard

import ssl
def is_server_ssl(v: object) -> TypeGuard[ssl.SSLContext | None]:
    return v is None or isinstance(v, ssl.SSLContext)

Prevention

When it happens

Trigger: loop.create_server(factory, host, port, ssl=True) or ssl=False; any code passing a boolean flag copied from an old tutorial.

Common situations: Pre-3.11-era code or copy-pasted examples using ssl=True; wrapping the ssl parameter in a feature flag (ssl=use_tls) where use_tls is a bool.

Understand the failure class

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/262a1668f5ad1bfd. Report an issue: GitHub.