python/cpython · error · TypeError
transport {transport!r} is not supported by start_tls()
Error message
transport {transport!r} is not supported by start_tls() What it means
Raised by BaseEventLoop.start_tls() when the transport passed in does not set the _start_tls_compatible attribute to True. start_tls() can only upgrade an existing plain TCP transport (from create_connection/create_server accept) to TLS; datagram transports, pipe/subprocess transports, and already-encrypted SSL transports cannot be upgraded.
Source
Thrown at Lib/asyncio/base_events.py:1348
server_side=False,
server_hostname=None,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None):
"""Upgrade transport to TLS.
Return a new transport that *protocol* should start using
immediately.
"""
if ssl is None:
raise RuntimeError('Python ssl module is not available')
if not isinstance(sslcontext, ssl.SSLContext):
raise TypeError(
f'sslcontext is expected to be an instance of ssl.SSLContext, '
f'got {sslcontext!r}')
if not getattr(transport, '_start_tls_compatible', False):
raise TypeError(
f'transport {transport!r} is not supported by start_tls()')
waiter = self.create_future()
ssl_protocol = sslproto.SSLProtocol(
self, protocol, sslcontext, waiter,
server_side, server_hostname,
ssl_handshake_timeout=ssl_handshake_timeout,
ssl_shutdown_timeout=ssl_shutdown_timeout,
call_connection_made=False)
# Pause early so that "ssl_protocol.data_received()" doesn't
# have a chance to get called before "ssl_protocol.connection_made()".
transport.pause_reading()
# gh-142352: move buffered StreamReader data to SSLProtocol
if server_side:
from .streams import StreamReaderProtocol
if isinstance(protocol, StreamReaderProtocol):View on GitHub (pinned to bc6749cc3b)
Solutions
- Verify the transport came from loop.create_connection() or an accepted TCP server connection before calling start_tls()
- If the connection is already TLS, do not call start_tls() again; use the existing SSL transport or reconnect with ssl= passed to create_connection()
- For custom transports, set _start_tls_compatible = True only if the transport is a plain socket stream compatible with the selector implementation
- Check the protocol stack: unwrapping TLS (transport.get_extra_info('ssl_object') / unwrap) then re-wrapping is not supported by start_tls()
Example fix
// before transport, protocol = await loop.create_datagram_endpoint(...) # UDP await loop.start_tls(transport, protocol, ctx) # TypeError // after transport, protocol = await loop.create_connection(factory, host, port) # TCP tls_transport = await loop.start_tls(transport, protocol, ctx)
Defensive patterns
Strategy: validation
Validate before calling
def can_start_tls(transport) -> bool:
return bool(getattr(transport, '_start_tls_compatible', False))
if not can_start_tls(transport):
raise RuntimeError(f'cannot upgrade {transport!r}; reconnect with ssl= instead')
tls_transport = await loop.start_tls(transport, protocol, ctx) Type guard
def is_start_tls_compatible(t: asyncio.Transport) -> TypeGuard[asyncio.Transport]:
return getattr(t, '_start_tls_compatible', False) is True Try / catch
try:
tls_t = await loop.start_tls(transport, protocol, ctx)
except TypeError as e:
if 'not supported by start_tls' in str(e):
transport.close() # fall back: reconnect with ssl= from the start
else:
raise Prevention
- Only call start_tls() on transports returned by create_connection() or accepted TCP sockets
- Track whether a connection is already TLS (e.g. transport.get_extra_info('ssl_object') is not None) and never double-upgrade
- For custom transports, set _start_tls_compatible only after verifying selector-based stream semantics
When it happens
Trigger: Calling loop.start_tls(transport, protocol, sslcontext) where transport is a datagram transport (create_datagram_endpoint), a _SelectorTransport pipe/stdin/stdout transport, an already-wrapped SSL transport, or a custom transport class that does not define _start_tls_compatible = True.
Common situations: Attempting STARTTLS-style upgrades (e.g. smtp, redis TLS upgrade) on a connection that is already TLS, or on a UDP-based protocol; passing the raw transport obtained from a transport/protocol pair that is not a socket stream transport; custom transport subclasses that forget the compatibility flag.
Related errors
- data: expecting a bytes-like instance, got {type(data).__nam
- Failed to fetch glossary.json
- d3-flame-graph library failed to load
- BUILD_GNU_TYPE is not defined
- Socket cannot be of type SSLSocket
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/c80450d793e79aac.
Report an issue: GitHub.