最稳妥的TLS配置是用ssl.create_default_context()配合context.wrap_socket(),需严格区分客户端(设server_hostname)和服务端(load_cert_chain、server_side=True、Purpose.CLIENT_AUTH),禁用弃用的ssl.wrap_socket()。

ssl.create_default_context() + context.wrap_socket() 是最稳妥的起点,但必须区分客户端和服务器侧的参数,否则会卡在握手失败或证书验证错误。
客户端连接时必须传 server_hostname
不加这个参数,context.wrap_socket() 会跳过主机名验证,但多数现代服务(尤其是启用了 SNI 的)会拒绝连接,报错类似 ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] 或静默断连。
实际写法必须显式指定:
secure_sock = context.wrap_socket(sock, server_hostname="example.com")- 即使连的是
127.0.0.1,只要服务端证书里 Subject Alternative Name(SAN)写了域名,就得填对应域名,不能填 IP - 如果服务端用的是自签名证书且你信任它,可临时禁用验证:
context.check_hostname = False和context.verify_mode = ssl.CERT_NONE,但仅限测试
服务端需要加载证书链且明确设 server_side=True
只调 wrap_socket() 不加载证书,会触发 ssl.SSLError: [SSL: SSL_HANDSHAKE_FAILURE]。关键点有三个:
- 证书文件(
certfile)和私钥(keyfile)路径必须存在且可读,推荐用绝对路径,避免相对路径引发FileNotFoundError - 上下文要设为
ssl.Purpose.CLIENT_AUTH,否则默认只做客户端用途,无法接受入站 TLS 握手 -
context.wrap_socket(client_socket, server_side=True)中server_side=True不可省略——漏掉会导致ssl.SSLEOFError或空响应
ssl.wrap_socket() 已弃用,优先用上下文对象
旧教程里常见的 ssl.wrap_socket(sock) 调用方式在 Python 3.12+ 已标记为弃用,运行时会抛 DeprecationWarning,未来版本可能移除。真正该用的是上下文模式:
- 客户端:用
ssl.create_default_context()创建上下文,再wrap_socket()包装 - 服务端:同上,但初始化时加
ssl.Purpose.CLIENT_AUTH,再load_cert_chain() - 若需控制加密套件(比如禁用弱算法),用
context.set_ciphers("ECDHE-ECDSA-AES256-GCM-SHA384"),而非操作底层 socket
证书不匹配是调试中最常卡住的地方
现象通常是连接建立后立刻断开,或 recv() 返回空字节。根本原因往往不是代码逻辑,而是证书配置细节:
立即学习“Python免费学习笔记(深入)”;
- 自签名证书必须用
openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365 -subj "/CN=localhost"生成,且CN或 SAN 必须与客户端传的server_hostname一致 - 服务端证书不能缺少私钥,也不能用密码保护的私钥(除非手动提供密码回调,增加复杂度)
- 客户端若用系统 CA 无法验证服务端证书(比如内网自签),必须额外加载 CA 文件:
context.load_verify_locations("ca-bundle.crt")
SSLError 或 ConnectionResetError,再对照这四点逐项核对。


















