python/cpython · error · ValueError

ssl_shutdown_timeout should be a positive number, got {ssl_s

Error message

ssl_shutdown_timeout should be a positive number, got {ssl_shutdown_timeout}

What it means

Raised in _SSLProtocol.__init__ as ValueError when an explicitly supplied ssl_shutdown_timeout is <= 0 (and not None). This timeout bounds the TLS shutdown/close_notify exchange when the connection is torn down; non-positive values are rejected because the graceful-shutdown wait would be degenerate.

Source

Thrown at Lib/asyncio/sslproto.py:290

                 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
        self._sslcontext = sslcontext
        # SSL-specific extra info. More info are set when the handshake
        # completes.
        self._extra = dict(sslcontext=sslcontext)

        # App data write buffering

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Use a positive duration: ssl_shutdown_timeout=5.
  2. Use None/omit the parameter for the default (constants.SSL_SHUTDOWN_TIMEOUT, 30s).
  3. Sanitize numeric config inputs at load time, rejecting non-positive timeout values early with a clear message.

Example fix

// before
await loop.create_connection(proto, 'h', 443, ssl=ctx, ssl_shutdown_timeout=0)
# ValueError

// after
await loop.create_connection(proto, 'h', 443, ssl=ctx, ssl_shutdown_timeout=5.0)
Defensive patterns

Strategy: validation

Validate before calling

def shutdown_timeout(value):
    if value is None:
        return None  # asyncio default (30s)
    value = float(value)
    if value <= 0:
        raise ValueError('ssl_shutdown_timeout must be > 0')
    return value

await loop.create_connection(proto, 'h', 443, ssl=ctx,
                              ssl_shutdown_timeout=shutdown_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 loop.create_connection(proto, 'h', 443, ssl=ctx, ssl_shutdown_timeout=t)
except ValueError as e:
    if 'ssl_shutdown_timeout' in str(e):
        await loop.create_connection(proto, 'h', 443, ssl=ctx)
    else:
        raise

Prevention

When it happens

Trigger: Passing ssl_shutdown_timeout=0 or a negative value to the asyncio SSL entry points (loop.create_connection, create_server, start_server, start_tls) which forward it into _SSLProtocol.

Common situations: Mirroring ssl_handshake_timeout tuning and setting both to 0 intending 'no timeout'; env-driven config parsing empty values as 0; CI configs that clamp all timeouts to 0 for speed.

Understand the failure class

Related errors


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