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 = NoneView on GitHub (pinned to bc6749cc3b)
Solutions
- Pass a positive number of seconds, e.g. ssl_handshake_timeout=10.
- Pass None (or omit the kwarg) to use the default (constants.SSL_HANDSHAKE_TIMEOUT, 60s).
- 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
- Pass None for the default timeout; never 0 as a 'disable' sentinel.
- Validate config-sourced numbers (env vars, YAML) as positive floats at load time.
- Keep timeout tuning centralized so bad values are caught in one validator.
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- ssl_shutdown_timeout should be a positive number, got {ssl_s
- ssl_handshake_timeout is only meaningful with ssl
- ssl_shutdown_timeout is only meaningful with ssl
- Server side SSL needs a valid SSLContext
- high (%r) must be >= low (%r) must be >= 0
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/75bd2c8715109a33.
Report an issue: GitHub.