python/cpython · error · ValueError

ssl_handshake_timeout should be a positive number, got {ssl_

Error message

ssl_handshake_timeout should be a positive number, got {ssl_handshake_timeout}

What it means

Raised in _SSLProtocol.__init__ as ValueError when an explicitly supplied ssl_handshake_timeout is <= 0 (and not None). The timeout bounds how long the TLS handshake may take before the connection is aborted; zero or negative durations are meaningless and rejected at construction.

Source

Thrown at Lib/asyncio/sslproto.py:284

    _handshake_start_time = None
    _handshake_timeout_handle = None
    _shutdown_timeout_handle = None

    def __init__(self, loop, app_protocol, sslcontext, waiter,
                 server_side=False, server_hostname=None,
                 call_connection_made=True,
                 ssl_handshake_timeout=None,
                 ssl_shutdown_timeout=None):
        if ssl is None:
            raise RuntimeError("stdlib ssl module not available")

        self._ssl_buffer = bytearray(self.max_size)
        self._ssl_buffer_view = memoryview(self._ssl_buffer)

        if ssl_handshake_timeout is None:
            ssl_handshake_timeout = constants.SSL_HANDSHAKE_TIMEOUT
        elif ssl_handshake_timeout <= 0:
            raise ValueError(
                f"ssl_handshake_timeout should be a positive number, "
                f"got {ssl_handshake_timeout}")
        if ssl_shutdown_timeout is None:
            ssl_shutdown_timeout = constants.SSL_SHUTDOWN_TIMEOUT
        elif ssl_shutdown_timeout <= 0:
            raise ValueError(
                f"ssl_shutdown_timeout should be a positive number, "
                f"got {ssl_shutdown_timeout}")

        if not sslcontext:
            sslcontext = _create_transport_context(
                server_side, server_hostname)

        self._server_side = server_side
        if server_hostname and not server_side:
            self._server_hostname = server_hostname
        else:
            self._server_hostname = None

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Pass a positive number of seconds, e.g. ssl_handshake_timeout=10.
  2. Pass None (or omit the kwarg) to use the default (constants.SSL_HANDSHAKE_TIMEOUT, 60s).
  3. Validate config-sourced timeout values (if v is not None and v <= 0: raise) before handing them to asyncio.

Example fix

// before
await asyncio.open_connection('h', 443, ssl=ctx, ssl_handshake_timeout=0)
# ValueError

// after
await asyncio.open_connection('h', 443, ssl=ctx, ssl_handshake_timeout=10.0)
Defensive patterns

Strategy: validation

Validate before calling

def handshake_timeout(value):
    if value is None:
        return None  # asyncio default (60s)
    value = float(value)
    if value <= 0:
        raise ValueError('ssl_handshake_timeout must be > 0')
    return value

await asyncio.open_connection('h', 443, ssl=ctx,
                              ssl_handshake_timeout=handshake_timeout(cfg))

Type guard

def is_positive_timeout(v) -> bool:
    return v is None or (isinstance(v, (int, float)) and not isinstance(v, bool) and v > 0)

Try / catch

try:
    await asyncio.open_connection('h', 443, ssl=ctx, ssl_handshake_timeout=t)
except ValueError as e:
    if 'ssl_handshake_timeout' in str(e):
        await asyncio.open_connection('h', 443, ssl=ctx)  # use default
    else:
        raise

Prevention

When it happens

Trigger: Passing ssl_handshake_timeout=0 or a negative number to loop.create_connection()/open_connection()/create_server()/start_tls (the kwarg is forwarded to _SSLProtocol).

Common situations: Using 0 as an 'infinite/disabled' sentinel (the actual way to get the default is None); configuration values loaded from files/env where an unset variable parses as 0; unit tests parameterizing timeouts including a 0 case.

Understand the failure class

Related errors


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