1. 先说清楚:每次浏览器加载 HTTPS 页面,背后到底发生了什么
你在浏览器地址栏里输入的https://,背后的基础设施就是 SSL 与 TLS。只要是涉及网站加密传输、接口安全调用、数据库 SSL 连接、企业内网证书配置的场景,这套协议几乎是无处不在。很多人背过“SSL 是安全套接层,TLS 是传输层安全”这句话,但真遇到问题就抓瞎——比如 curl 突然报unexpected eof while reading,MySQL 连不上报 SSL 错误,或者浏览器直接提示“证书链是由不受信任的颁发机构签发的”。这篇文章不只是把概念讲清楚,我会把 SSL/TLS 从原理到握手过程、从证书体系到密码套件,再到常见报错的排查思路全部过一遍,适合计算机网络课程复习、正在做 HTTPS 部署的开发者,以及被各类 SSL 证书报错折磨的运维同学。
我最开始接触 SSL/TLS 是因为做全站 HTTPS 改造,当时生产环境突然一堆客户端连不上,排查了大半天才发现是 TLS 版本和密码套件不匹配导致的。后来把这套协议从 RFC 文档到 OpenSSL 命令逐层啃了一遍,再回头看那些报错,基本就是几个固定模板。这篇我就按自己的理解顺序来写:先厘清 SSL 和 TLS 的关系,再拆解握手过程,接着讲证书与信任链,然后聊密码套件和已知漏洞,最后集中整理一份排错实战笔记。
2. 核心概念拆解:SSL 和 TLS 到底是什么关系
2.1 名字里的历史:为什么现在都说 TLS,却还在叫 SSL 证书
SSL 全称是 Secure Sockets Layer,安全套接层,最早由 Netscape 在 1995 年前后推出,从 SSL 1.0 一直演进到 3.0。TLS 全称是 Transport Layer Security,传输层安全,是 IETF 在 SSL 3.0 基础上标准化而来的后续版本,TLS 1.0 对应 SSL 3.1 的改进版,之后是 TLS 1.1、1.2,一直到现在的 TLS 1.3。
也就是说,从协议标准的角度看,SSL 是一个已经被淘汰的旧名字,今天真正在跑的都是 TLS。但“SSL 证书”这个叫法沿用至今,因为证书本身的结构和格式(X.509)从 SSL 时代就没变过,各大云厂商提供的“SSL 证书”服务,实际签发的就是符合 X.509 标准、用于 TLS 握手的数字证书。你去阿里云申请免费证书,页面写的是“SSL 证书”,部署到 Nginx 之后生效的是 TLS 1.2/1.3 协议,这中间没有矛盾,就是历史叫法延续下来的习惯。
2.2 协议栈中的位置:SSL/TLS 卡在 TCP 和 HTTP 之间
理解 SSL/TLS 最简单的方式是看它在网络分层里的位置。它不替代 TCP,也不替代 HTTP,而是插入在传输层和应用层之间。普通的 HTTP 请求是直接基于 TCP 传输,加了 SSL/TLS 之后,HTTP 的数据先交给 SSL/TLS 层做加密、完整性校验,然后才交给 TCP 发送出去。
用生活类比说,TCP 是一条可靠的运输管道,保证货物(数据包)不丢不重不乱序;HTTP 是管道里传输的具体货物;SSL/TLS 就是在货物装箱时加了一层带锁的密封箱——只有通信双方有钥匙能打开,别人即使截获了管道里的东西,看到的也只是密文。这也是为什么 HTTPS 的下层还是 TCP,建立连接时先做 TCP 三次握手,然后才做 TLS 握手,之后应用数据才在 TLS 隧道里传输。
2.3 加密体系的三根支柱:对称加密、非对称加密、哈希算法
TLS 能安全工作,核心靠三样东西配合:
- 对称加密:加密和解密用同一把密钥,速度快,适合加密大量实际数据,缺点是密钥怎么安全地告诉对方。
- 非对称加密:公钥加密、私钥解密,或者反过来,速度慢,但解决了密钥分发问题。TLS 用非对称加密来协商出对称密钥,而不是直接加密整段数据。
- 哈希算法:把任意长度的数据映射成固定长度的摘要,用于保证数据完整性,防止密文被篡改。TLS 握手过程中的 Finished 消息、证书签名验证都依赖哈希。
这三者的配合逻辑是:先用非对称加密传递一个“预主密钥”,双方各自计算出最终的对称会话密钥,然后用这个对称密钥加密后续所有应用数据,同时用哈希摘要校验每个记录的完整性。非对称加密只用在握手阶段,代价小;对称加密用在数据传输阶段,性能高。这个设计是整个 TLS 协议的基石,后面讲握手时会反复用到。
3. TLS 握手全过程拆解:从 TCP 三次握手到加密隧道建立
3.1 握手的目标:双方要确认三件事
TLS 握手的本质,是客户端和服务器在正式传输数据之前,完成三个确认:
- 确认协议版本(比如都支持 TLS 1.3,还是至少要退到 TLS 1.2)
- 确认双方身份(服务器必须出示证书证明自己是它声称的那个实体)
- 协商出本次会话专用的对称加密密钥
我经常跟人比喻,TLS 握手就像两个素未谋面的人第一次在咖啡馆接头:先问“你是哪个组织派来的”(证书验证),再确认“今天暗号是什么”(密钥协商),最后各自把暗号在心里默念一遍确认没有听错(Finished 消息),之后才开始谈正事。
3.2 以 TLS 1.2 为例:完整握手流程逐步走一遍
TLS 1.2 的握手流程,分为以下几个步骤:
第一步,客户端发送 ClientHello。内容是:客户端支持的 TLS 协议版本列表(比如 TLS 1.2 和 TLS 1.3)、支持的密码套件列表、一个随机数 client_random。
第二步,服务器收到后回复 ServerHello。内容是:双方最终选定的协议版本和密码套件、服务器生成的随机数 server_random。随后服务器还会发送自己的证书(Certificate)、密钥交换参数(ServerKeyExchange),并要求客户端出示证书的话也在这一步发送 CertificateRequest,最后发 ServerHelloDone 表示“我这边发完了”。
第三步,客户端验证服务器证书。验证证书链是否可信、证书域名是否匹配、证书是否过期、是否被吊销。如果服务器要求客户端证书,客户端也要发送自己的证书和签名。
第四步,客户端生成 pre-master secret,用服务器的公钥加密后发给服务器(ClientKeyExchange)。双方此时都持有 client_random、server_random 和 pre-master secret,各自独立计算出相同的 master secret,再从这个 master secret 派生出加密密钥、MAC 密钥和 IV。
第五步,客户端发送 ChangeCipherSpec,表示“接下来我发的消息都是加密的了”,然后发送 Encrypted Handshake Message(Finished 消息),内容是之前所有握手消息的哈希值。
第六步,服务器也发送 ChangeCipherSpec 和 Finished 消息。双方验证 Finished 消息后,握手完成,之后的应用数据全部走加密通道。
整个过程里最容易忽略的细节是:client_random、server_random 和 pre-master secret 三个输入缺一不可。即使 pre-master secret 泄露,如果没有随机数参与,会话密钥也无法被直接推导出来。这就是前向保密的基础设计逻辑。
3.3 TLS 1.3 的握手变化:更少往返,更安全的默认配置
TLS 1.3 对握手做了大幅简化。最大的变化是把之前需要两次网络往返(2-RTT)的流程压缩成一次往返(1-RTT),并且支持 0-RTT 恢复机制——客户端如果之前和服务器通信过,可以在第一个数据包就携带加密数据,减少延迟。
TLS 1.3 还做了两个重要决定:一是废除了 RSA 密钥交换方式,强制使用 ECDHE 等前向保密算法;二是删掉了一堆旧的、不安全的密码套件,只保留了 AEAD 类算法(比如 AES-GCM 和 ChaCha20-Poly1305)。这意味着服务器配置里只要启用了 TLS 1.3,那些 3DES、CBC 模式的旧套件就直接失效,大量历史漏洞从根上被堵住了。
实际部署时的建议是:如果服务端和客户端都支持,优先启用 TLS 1.3,保留 TLS 1.2 作为兼容兜底。TLS 1.0 和 1.1 已经属于事实上的淘汰协议,2020 年后主流浏览器已经禁止,企业内网如果还有老客户端依赖这两个版本,需要尽快推动升级。
3.4 我踩过的坑:握手超时与 Wireshark 抓包观察
有一次排查一个内部系统的连接超时问题,客户端一直报“TLS handshake timeout”。正常思路是先看网络通不通,但抓包之后发现 TCP 三次握手都成功了,ClientHello 也发出去了,服务器却迟迟没有回包。后来在服务器上用openssl s_server试本地握手,发现服务器进程连本地握手都会卡住,最后定位到是证书私钥文件权限导致进程读取时挂起。
建议大家在排查握手问题时,不要只看应用层日志,直接在客户端用openssl s_client -connect host:port -msg命令看握手消息的每一帧。-msg参数会打印所有握手消息的明文内容,能清楚看到是停在 ServerHello 还是 Certificate 阶段。Wireshark 里加一个tls过滤器,配合Follow TCP Stream,基本能还原完整的握手过程。
4. 证书体系与信任链:为什么浏览器信任你的服务器
4.1 X.509 证书和 CA:谁来证明你是你
服务器在握手时出示的证书,本质上是一个包含公钥、域名、有效期、签名算法和签发者信息的数字文件。证书的格式标准是 X.509,整个体系依赖 CA(Certificate Authority,证书颁发机构)来背书。
CA 的作用,就是“谁特么认识你是谁”的解药。服务器自己生成一个证书,浏览器是不信的,因为任何人都可以自称自己是某个网站。但 CA 签发的证书,浏览器会通过内置的根证书列表来验证——你用的 Windows、macOS、Linux 发行版里都预置了一批受信任的根证书,这些根证书的私钥由 CA 机构严格保管。当服务器出示一张由 CA 签发的证书时,浏览器会沿着证书链一路验证到根证书,确认这条链上的每一级签名都有效,才最终信任这张证书。
证书链的典型结构是:终端实体证书(你的服务器证书)→ 中间 CA 证书 → 根 CA 证书。实际部署时,服务器只需要配置终端实体证书和中间证书,根证书是内置在客户端的,不需要发送。
4.2 证书校验的四个检查项:顺序不能乱
客户端校验服务器证书时,一般做四件事:
- 证书链验证:从服务器证书往上逐级验证签名,直到内置的根证书。
- 有效期检查:证书的 notBefore 和 notAfter 两个字段,当前时间必须落在区间内。
- 域名匹配:检查证书中的 subjectAltName 或 CN 字段是否匹配当前访问的域名。
- 吊销状态检查:通过 OCSP(在线证书状态协议)或 CRL(证书吊销列表)确认证书没有被吊销。
这四项里最容易出问题的是域名匹配和证书链不完整。证书链不完整在浏览器里通常能自动修复(浏览器会尝试下载缺失的中间证书),但很多非浏览器客户端不会做这个补齐操作,就会直接报错。我见过很多运维在 Nginx 里只配置了服务器证书,忘了把 CA 签发的中间证书追加进ssl_certificate文件里,结果手机 App 连不上,浏览器却一切正常。
4.3 自签名证书与私有 CA:内网环境的取舍
内网系统有时图省事直接用自签名证书,客户端访问时总弹出安全警告。自签名证书不是不能用于生产,而是要用“私有 CA”的思路来做,而不是直接给每台服务器生成一张“自签”证书。
正确的做法是:在内网搭建一个私有 CA(比如用 OpenSSL 建 CA 根,或者用企业内部的 AD CS 服务),然后给各服务器签发由这个私有 CA 盖章的证书。客户端需要安装这个私有 CA 的根证书,并把“完全信任该证书颁发机构”打开。这样所有服务器证书都能由同一根 CA 验证,客户端只需要信任一个根,不用逐个信任每张服务器证书。
我在公司内网就是这么做的:用 OpenSSL 创建了一个独立的根 CA,所有内部系统的 HTTPS 证书都从它签发,开发者入职时把根证书加入系统受信任区,之后再访问任何内部系统都不会弹警告。这个方案既解决了自签名警告问题,也避免了内外网证书体系混乱。
4.4 SNI 与证书选择:一个 IP 上挂了多个域名时怎么选证书
SNI(Server Name Indication)是 TLS 的一个扩展,解决的是“一个服务器 IP 对应多个域名、该出示哪张证书”的问题。客户端在 ClientHello 里带上自己要访问的域名(server_name),服务器根据这个域名选择合适的证书返回。
如果服务器配置了多个证书,但没有正确启用 SNI,就会出现客户端拿到一个不匹配的证书。Java 某些老版本的 HTTP 客户端不支持 SNI,就会报unrecognized_name之类的错误,实际原因就是你访问的域名和服务器默认返回的证书域名不一致。
排查这个问题的命令是:
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -showcerts-servername参数就是手动指定 SNI,能验证服务器在你的域名上返回的证书是否正确。Nginx 里同一 server block 中配置多个域名时,也要确保每个域名都有对应的证书文件和正确的server_name匹配。
4.5 证书密钥太小的报错:ee key too small 是什么意思
热词里有一条ssl routines:ssl_ctx_use_certificate:ee key too small的报错,这属于现在越来越多客户端收紧密钥长度要求导致的。很多现代库和浏览器规定 RSA 密钥不能小于 2048 位,ECDSA 密钥不能小于 224 位。如果你还在用老配置里生成的 1024 位 RSA 证书,新版本客户端会直接拒绝握手。
解决方案就很直接:用更强的密钥重新生成证书。如果是自己生成的私钥和 CSR,用 OpenSSL 生成时明确指定位数:
openssl genrsa -out server.key 2048或者用更现代的写法:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server.key申请云厂商证书时也一样,选 RSA 2048 或更高的密钥长度。再小的证书劝你趁早换掉,这不只是兼容性问题,更是安全问题,1024 位 RSA 在算力面前已经不再安全。
5. 密码套件与安全漏洞:CVE-2016-2183、Sweet32 和其他雷区
5.1 一组密码套件包含什么内容
密码套件(Cipher Suite)是一组算法的集合名称,比如ECDHE-RSA-AES128-GCM-SHA256,这一长串名字里包含了 4 个信息:
- 密钥交换算法:ECDHE,决定如何协商出对称密钥。
- 身份认证算法:RSA,决定服务器证书使用哪种签名算法。
- 对称加密算法:AES128-GCM,决定实际数据用什么算法加密。
- 消息完整性算法:SHA256,决定校验数据完整性用什么哈希。
套件的命名规则是一脉相承的,看懂这套命名之后,你再看服务器的ssl_ciphers配置,就能判断出哪些是安全的、哪些是历史的遗留。
5.2 CVE-2016-2183 和 Sweet32 为什么危险
热词里反复出现 CVE-2016-2183,这个漏洞也被称为 Sweet32,问题出在 64 位块大小的分组密码上——典型代表就是 3DES(DES-EDE3-CBC)。64 位块大小意味着分组空间只有 2 的 64 次方,当加密的数据量足够大时,会产生生日碰撞,攻击者可以利用碰撞泄露部分明文信息。
实际影响是,当一个 TLS 连接用 3DES 加密传输的数据量达到几十 GB 量级时,攻击者理论上可能恢复出比如 Cookie 之类的敏感数据。这个漏洞存在于 TLS 协议内部,只要密码套件列表里还有 3DES,就可能被扫描器标记。很多安全扫描系统报“SSL/TLS 协议信息泄露漏洞(CVE-2016-2183)【原理扫描】”,原理就是检测到服务端还支持 3DES 类套件。
修复方式:在服务端配置里彻底移除 3DES 相关的密码套件。Nginx 示例:
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';这样配置之后,服务端在协商时就不会把 3DES 列入支持列表。如果只是临时启用,也可以最低限度禁用DES-CBC3-SHA这一个套件,但为了保险起见,用上面这种白名单方式更干净。
5.3 常见高危密码套件和漏洞清单
除了 CVE-2016-2183,还有几个历史上有名的 TLS 安全事件,值得配置时注意:
| 漏洞事件 | 存在问题 | 受影响算法/套件 | 修复思路 |
|---|---|---|---|
| POODLE(CVE-2014-3566) | 对 CBC 模式的填充字节进行 padding oracle 攻击 | SSL 3.0 及部分 CBC 套件 | 禁用 SSL 3.0,禁用 CBC 模式套件 |
| Heartbleed(CVE-2014-0160) | OpenSSL 心脏出血,内存泄露 | OpenSSL 1.0.1 的 heartbeat 扩展 | 升级 OpenSSL 版本 |
| BEAST | 针对 TLS 1.0 CBC 模式的攻击 | TLS 1.0 的 CBC 套件 | 优先使用 TLS 1.2+,或启用 GCM 套件 |
| ROBOT | 恢复 RSA 密钥交换方式 | RSA key exchange | 禁用 RSA 密钥交换套件 |
| Logjam | 降级攻击,针对 DH 参数 | 512 位 DH 参数 | 使用 ≥2048 位 DH 参数,或改用 ECDHE |
| Sweet32(CVE-2016-2183) | 64 位块大小密码的生日碰撞 | 3DES、Blowfish 等 | 禁用 64 位块大小套件 |
服务端配置密码套件时,大原则是优先使用 AEAD 算法(GCM、CCM、ChaCha20-Poly1305)作为对称加密,优先使用 ECDHE 作为密钥交换算法,禁用 RSA 密钥交换,禁用 CBC 模式除非没有其他选择。
5.4 怎么用命令查看服务端当前支持的套件
我有一次被安全团队要求整改,就是用 nmap 的脚本扫描服务端套件列表。其实用 OpenSSL 就够了,最简单直接:
openssl s_client -connect yourdomain.com:443 -cipher 'ECDHE' 2>/dev/null | grep Cipher如果输出显示握手成功,说明服务端支持至少一个 ECDHE 系列的套件;如果是handshake failure,说明一个都不支持。同样地,把-cipher 'DES-CBC3-SHA'带上去测试,如果握手成功,说明服务端还在支持 3DES——那就该修了。
也可以直接抓握手包看 ServerHello 里返回的 cipher suite 字段,或者用在线测试工具 SSL Labs 做一次完整的配置评估,它会列出所有支持的套件并给出等级评分,整改的时候按评分条目逐项处理就行。
6. 实操排错:SSL/TLS 高频报错与处理速查
6.1 报错速查表:从 curl、浏览器、数据库到中间件
结合日常运维和学习中遇到的报错,我把最常见的几类整理成一个速查表:
| 报错场景 | 典型报错信息 | 根因方向 | 处理建议 |
|---|---|---|---|
| curl 请求 HTTPS | curl: (35) error:0A000126: SSL routines::unexpected eof while reading | 服务端握手不完整,可能是 TLS 版本/套件不匹配、证书配置不完整 | 查看服务端日志,用openssl s_client测握手,检查证书文件和密码套件配置 |
| MySQL 客户端 | mysql ssl connection error | 客户端没有安装 CA 证书,或 mysql 用户认证要求 SSL | 为客户端配置--ssl-ca,检查 MySQL 服务端 SSL 配置 |
| 浏览器访问 | ERR_SSL_PROTOCOL_ERROR | 服务端启用了不支持的协议版本,或协议配置冲突 | 检查服务端 TLS 版本设置,确认没有只开 TLS 1.3 而客户端不支持的情况 |
| ODBC/SQL Server | 证书链是由不受信任的颁发机构签发的 | 客户端没有信任服务端证书的根 CA | 在客户端安装对应 CA 根证书,或改用系统级信任库 |
| Java 客户端 | unrecognized_name alert | SNI 不匹配,客户端访问的域名和服务端证书域名不一致 | 检查证书域名,确认 SNI 配置 |
| PowerShell | 请求被中止: 未能创建 SSL/TLS 安全通道 | 系统默认只启用 TLS 1.0/1.1,或代理冲突 | 启用 TLS 1.2,检查 Windows 注册表及 IE 设置 |
| pip 下载包 | SSL support is missing | 本地 Python/OpenSSL 环境异常 | 重装 OpenSSL,确认 Python 的 SSL 模块正常编译 |
| Nacos 启动 | SSL 证书配置错误 | 证书路径或密码配置错误 | 核对配置文件中证书格式、密码和路径,确保证书为 PKCS12 或 PEM 对应正确的加载方式 |
| 远程桌面 | SL/TLS 协议信息泄露漏洞(CVE-2016-2183) | RDP 服务端启用了 3DES 套件 | 组策略禁用 3DES 加密套件,启用 AES 加密 |
| 阿里云证书访问 | 证书不完整 | 漏配中间证书 | 下载证书时使用云厂商提供的完整证书链文件,一般包含.pem和.key两部分 |
6.2 案例一:curl 报 unexpected eof 实际是版本不匹配
有一次我用 curl 访问一个外部 API,直接报error:0A000126: SSL routines::unexpected eof while reading。curl 已经发完 ClientHello,对方服务器却直接断了连接。我用curl -v看到发抖的细节:
curl -v https://api.example.com输出到CONNECTED之后马上就是RECEIVED EOF。这个现象几乎可以断定,是服务器对 curl 发起的握手请求不欢迎——最常见的原因就是服务器只支持 TLS 1.3,而系统 curl 的 OpenSSL 版本太老,只支持到 TLS 1.2。
排查思路分两步:第一步,用系统的 OpenSSL 测试服务器确实支持哪些版本:
openssl s_client -connect api.example.com:443 -tls1_2 openssl s_client -connect api.example.com:443 -tls1_3第二步,如果确认服务器只支持 TLS 1.3,那就升级本地的 curl 和 OpenSSL。如果服务器两边都支持,再看看是不是 HTTP/2 的协商问题,可以加--http1.1排除。
这个案例提醒我,遇到 curl 报错时,不要第一时间怀疑证书,先分清是协议版本问题、证书验证问题,还是套件不匹配问题。最简单粗暴的排查方式永远是加-v参数看细节。
6.3 案例二:MySQL SSL 连接失败的三种修复方式
MySQL 的 SSL 连接报错,常见于实际生产环境。我遇到过一个场景:安全要求数据库连接必须走 SSL,但应用连接时报SSL connection error,原因是 MySQL 服务端虽然开启了 SSL,但客户端需要指定 CA 证书才能验证服务器身份。
修复步骤一般是这三步:
第一,确认服务端 SSL 状态:
SHOW VARIABLES LIKE '%ssl%';如果have_ssl是YES,说明服务端 SSL 已开启。
第二,客户端连接时显式指定 CA 证书:
mysql -h host -u user -p --ssl-ca=/path/to/ca.pem --ssl-mode=VERIFY_IDENTITY其中--ssl-mode=VERIFY_IDENTITY表示不仅要验证证书链,还要校验证书里的主机名与连接地址匹配。
第三,如果应用框架连接 MySQL 时走的是 JDBC 或其他驱动,需要在连接字符串加上 SSL 参数,比如 JDBC 的 URL 里加:
jdbc:mysql://host:3306/db?useSSL=true&requireSSL=true&verifyServerCertificate=true&serverCertificate=/path/to/ca.pem还有一种情况是 MySQL 用户创建时带了REQUIRE SSL属性,应用如果没走 SSL 就会直接报权限错误。检查用户表的ssl_type字段就能确认。
6.4 案例三:SNI 玄学——同一 IP 多域名下的证书选错问题
SNI 问题我在前面提到过一次,这里用一个具体案例说明。公司一台 Nginx 服务器上部署了 A 和 B 两个站点的 HTTPS,A 的证书配置正常,B 是后来加的。B 的站点一直有人报“证书不匹配”,但用浏览器访问却是好的。
我openssl s_client -connect ip:443 -servername b.example.com一测,发现返回的证书确实是 A 的。原因很典型:Nginx 里 B 的 server block 配置了证书,但服务器块的listen 443 ssl指令中没有对应正确的 server_name 排序,或者 B 的证书文件路径写错了,Nginx 启动时加载了第一份默认证书。
排查完把 B 的ssl_certificate路径改正,并且确认每个 server block 都配了独立证书后,问题解决。SNI 的坑就在于它工作在握手早期,服务器必须在 ClientHello 里读到域名才能决定用哪个证书,如果配置不明确,就会退回默认证书。
7. 部署落地:从申请证书到服务端配置优化的完整路径
7.1 怎么快速申请一张免费证书并配置
免费证书的主流渠道是阿里云的数字证书管理服务。申请免费 SSL 证书的路径一般是:控制台选择“证书服务” → 申请免费证书 → 填写域名、验证方式(DNS 验证或文件验证)→ 审核通过后下载证书。
阿里云免费证书有 90 天有效期,到期后需要续期。热词里提到“阿里云 ssl 证书免费续期”,实际操作是:证书到期前,阿里云会提示可重新申请,申请审批完成后下载新证书替换上去。如果域名在阿里云解析,直接一键 DNS 验证,5 分钟内就能签发。
拿到证书后,以 Nginx 为例,配置片段是:
server { listen 443 ssl http2; server_name yourdomain.com; ssl_certificate /etc/nginx/ssl/yourdomain.com.pem; ssl_certificate_key /etc/nginx/ssl/yourdomain.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...'; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1h; }ssl_certificate文件里最好把服务器证书和中间证书合并在一起,ssl_certificate_key对应私钥。私钥权限建议设为 600,避免其他用户读取。
7.2 用 OpenSSL 自己生成证书的完整过程
如果需要内网测试证书,一行命令生成自签名证书是最快的:
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=yourdomain.com"如果要用私有 CA 签发,流程是我在 4.3 节里说的那套:
第一步,生成 CA 根证书:
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 3650 -nodes -subj "/CN=Internal Root CA"第二步,生成服务器私钥和 CSR:
openssl req -newkey rsa:2048 -keyout server.key -out server.csr -nodes -subj "/CN=internal.example.com"第三步,用 CA 签名:
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 825生成的server.crt要确认包含 SAN 字段,如果客户端做了主机名校验,只写 CN 是过不去的。用带扩展的配置文件生成 CSR 更稳妥,把subjectAltName=DNS:internal.example.com加上。
7.3 配置检查清单:上线前按这个表过一遍
部署 HTTPS 之前,我习惯按下面这份清单检查,能省掉大量排错时间:
- 证书域名:证书上的 SAN 和实际访问域名完全一致。
- 证书链:证书文件里包含完整的中间证书。
- 私钥匹配:证书公钥和私钥能对上,用下面命令验证:
两个哈希一致才说明匹配。openssl x509 -in server.crt -noout -pubkey | openssl pkey -pubin -outform DER | sha256sum openssl pkey -in server.key -pubout -outform DER | sha256sum - 协议版本:至少 TLS 1.2,能开 TLS 1.3 就开。
- 套件策略:白名单方式只留 AEAD 类套件,禁用 3DES、RC4、CBC。
- 证书有效期:部署后顺手看下到期时间,设置续期提醒。
- 敏感配置:关闭 TLS 压缩、关闭 session ticket 的 RSA key exchange,如果不需要双向认证,不要开启 client certificate 认证。
7.4 Nacos 这类中间件配置 SSL 的注意事项
热词里提到 Nacos 中间件配置 SSL 证书,这属于中间件叠加 TLS 的典型案例。Nacos 默认支持 HTTP,如果要开启 HTTPS,需要在配置文件中修改server.ssl.enabled=true并指定证书路径,核心配置项大致是:
server.ssl.enabled=true server.ssl.key-store=classpath:cert/nacos.p12 server.ssl.key-store-password=yourpassword server.ssl.key-store-type=PKCS12这里最容易出问题的是证书格式。Nacos 基于 Spring Boot,SSL 配置默认读取 PKCS12 格式的 keystore。如果你只有 PEM 格式的证书和私钥,要先用 OpenSSL 转换成 PKCS12:
openssl pkcs12 -export -in server.crt -inkey server.key -out nacos.p12 -name nacos -passout pass:yourpassword另一个坑是客户端访问 Nacos 时,如果服务端证书是自签或私有 CA 签发,客户端需要先信任 CA 根;否则报错就是 SSL 证书链不可信。Nacos 这个场景很多人上来直接用自签名证书,然后客户端连不上,最后发现是信任链问题而不是 Nacos 本身的问题。
8. 我的一些操作心得与建议
写了这么多,最后说几句实在话。
SSL/TLS 这个主题看起来是计算机网络课本里的一章,但实际上它的知识密度非常高,横跨密码学、网络协议、系统配置、运维排错。我自己学习时走过弯路,刚开始只背概念,后来被生产环境报错反复教育,才真正把握手、证书、套件这些术语串起来。如果你也是正在复习计算机网络的学生,我建议不要把 TLS 只是当一个考点背,而是搭一个本地环境,用 OpenSSL 生成证书、起一个 HTTPS 站点、再用openssl s_client观察握手过程,这样印象会深很多。
如果你是被各种 SSL 报错折磨的开发者或运维,我最大的建议是养成“抓包看握手”的习惯。报错信息往往只说“SSL 错误”,但到底是版本、证书还是套件问题,只有看握手阶段停在哪一步才能判断。下一篇文章我打算写 OpenSSL 命令的完整用法,从生成证书到调试握手一条龙,把这篇里的命令操作展开细讲。在那之前,如果你按照本文的方案配置或排错,遇到任何新报错,欢迎带着输出结果来交流。