一、TLS 在协议栈中的位置
TLS 不是替代 TCP,而是插在 TCP 和应用层之间的加密层:
HTTP
↓
TLS ← 我们在这
↓
TCP
↓
IP
HTTPS = HTTP over TLS,默认端口 443。TLS 对上层应用完全透明——应用只管读写加密字节流,握手、加密、完整性校验全由 TLS 层处理。
二、TLS 握手完整流程(以 ECDHE 为例)
现代生产环境 99% 使用 ECDHE(椭圆曲线迪菲-赫尔曼临时密钥交换),核心优势是前向安全——每次会话用临时密钥对,即使服务器私钥日后泄露,也解密不了历史流量
客户端 服务端
|------------- ClientHello ---------------->|
| · 支持的 TLS 版本(1.2 / 1.3) |
| · 随机数 ClientRandom |
| · 密码套件列表 |
| · SNI(Server Name Indication) |
| · 支持的签名算法 |
| |
|<------------ ServerHello -----------------|
| · 选定的 TLS 版本 |
| · 随机数 ServerRandom |
| · 选定的密码套件 |
| |
|<------------ Certificate ----------------|
| · 叶子证书(你的域名证书) |
| · 中间 CA 证书(可能多张) |
| |
|<------ ServerKeyExchange -----------------|
| · 椭圆曲线参数 |
| · 服务端临时公钥 |
| · 服务端签名(用证书私钥签,防篡改) |
| |
|<------ ServerHelloDone -------------------|
| |
|------ ClientKeyExchange ---------------->|
| · 客户端临时公钥 |
| |
|------ ChangeCipherSpec ----------------->| (TLS 1.2 及之前)
|------ Finished ------------------------->|
| · 验证握手完整性的 MAC |
| |
|<----- ChangeCipherSpec ------------------|
|<----- Finished --------------------------|
| |
|======== 加密通信开始 =====================|
TLS 1.3 的变化(简要)
- 握手从 2-RTT 缩减到 1-RTT(0-RTT 可选)
- 移除 RSA 密钥交换、RC4、CBC 等不安全算法
- 加密扩展:ServerHello 之后的所有消息都加密
- 签名算法独立协商
三、证书链:最容易被坑的地方
1、信任链结构
根 CA(Root CA)
│ 自签名,预置在操作系统/浏览器信任库
│
├── 中间 CA 1(由根 CA 签发)
│ │
│ ├── 中间 CA 2(由中间 CA 1 签发)
│ │ │
│ │ └── 叶子证书(你的域名证书,由中间 CA 2 签发)
│ │
│ └── 其他叶子证书...
│
└── 其他中间 CA...
2、验证流程(客户端侧)
1. 拿到叶子证书
2. 读取 Issuer 字段 → 找到签发者
3. 逐级向上找,直到根 CA
4. 根 CA 在本地信任库里?→ 信任链完整 ✓
5. 每一步验证签名 + 有效期 + 吊销状态
最常见故障:中间证书缺失
服务端只配了叶子证书(忘记拼接中间 CA)
↓
客户端收到后找不到签发者
↓
"NET::ERR_CERT_AUTHORITY_INVALID"
"证书不受信任"
验证方法:
# 看服务端实际返回的证书链
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
openssl crl2pkcs7 -nocrl -certfile /dev/stdin | \
openssl pkcs7 -print_certs -noout
# 如果只输出一张证书 = 缺中间 CA
四、证书格式与运维操作
| 格式 | 扩展名 | 编码 | 说明 |
|---|---|---|---|
| PEM | .pem, .crt, .key, .csr | Base64 文本 | 最常用 |
| DER | .der, .cer | 二进制 | Windows/Java |
| PKCS#12 | .p12, .pfx | 二进制 | 证书+私钥打包 |
| PKCS#7 | .p7b, .p7c | Base64 | 证书链(不含私钥) |
日常运维命令
# 查看证书详情
openssl x509 -in cert.pem -text -noout
# 查看有效期
openssl x509 -in cert.pem -dates -noout
# 查看 SAN(Subject Alternative Name)
openssl x509 -in cert.pem -ext subjectAltName -noout
# 验证私钥和证书是否匹配
openssl x509 -noout -modulus -in cert.pem | md5sum
openssl rsa -noout -modulus -in key.pem | md5sum
# 两个 md5 相同 = 匹配 ✓
# 查看 CSR 内容
openssl req -in request.csr -text -noout
# PEM → PKCS12
openssl pkcs12 -export -in fullchain.pem -inkey key.pem -out cert.p12
# 从 PKCS12 提取证书
openssl pkcs12 -in cert.p12 -nokeys -out cert.pem
# 从 PKCS12 提取私钥
openssl pkcs12 -in cert.p12 -nocerts -out key.pem -nodes
五、SNI:一个 IP 多张证书的关键
一个公网 IP 的 443 端口只能被一个进程监听,但上面跑了多个 HTTPS 站点(不同域名)。服务端怎么知道用哪张证书?
解决:SNI(Server Name Indication)
ClientHello 中携带 SNI 扩展字段发给服务端:
sni = "api.example.com"
服务端根据 SNI 选择对应的证书和虚拟主机配置
排错:
# 不带 SNI(用 IP 直连)
openssl s_client -connect 93.184.216.34:443
# 可能返回默认证书或握手失败
# 带 SNI
openssl s_client -connect 93.184.216.34:443 -servername api.example.com
# 返回正确证书
# 验证返回的证书域名
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -subject -ext subjectAltName
六、HTTPS 排障闭环
用户报"HTTPS 访问异常"
↓
┌───────────────────────────────┐
│ 1. 端口连通性 │
│ nc -zv example.com 443 │
└───────┬───────────────────────┘
↓ 不通
防火墙 / 安全组 / 服务未启动
↓ 通
┌───────────────────────────────┐
│ 2. 证书有效性 │
│ openssl s_client -connect │
└───────┬───────────────────────┘
↓ 证书报错
├─ 过期 → 续签替换
├─ 域名不匹配 → 检查 SAN / SNI
├─ 不受信任 → 缺中间证书
├─ 自签名 → 客户端未加信任
└─ 私钥不匹配 → 重新配对
↓ 证书 OK
┌───────────────────────────────┐
│ 3. 握手能否完成 │
│ 看 s_client 输出末尾 │
└───────┬───────────────────────┘
↓ 卡在 ClientHello
中间设备拦截(WAF/IPS/代理)
↓ 卡在 ServerHello
服务端 TLS 配置错误 / 性能问题
↓ 握手完成但业务异常
├─ HTTP→HTTPS 重定向循环
├─ 应用层逻辑错误
└─ 后端服务异常
↓ 部分客户端异常
├─ TLS 版本不兼容
├─ 密码套件无交集
├─ 中间证书老客户端不信任
└─ SNI 不支持
- nc -zv baidu.com 443 测试到域名的443端口通不通
排障命令大全
# 完整握手测试(最常用)
openssl s_client -connect example.com:443 -servername example.com
# 显示完整证书链
openssl s_client -connect example.com:443 -showcerts
# 测试特定 TLS 版本
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# 测试特定密码套件
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'
# 只看证书有效期和域名
openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -dates -subject -ext subjectAltName -noout
# curl 详细输出
curl -vI https://example.com
curl -vI --resolve example.com:443:93.184.216.34 https://example.com
# 测试 OCSP 吊销状态
openssl ocsp -issuer intermediate.pem -cert cert.pem \
-url http://ocsp.example.com -text
# 在线全面检测
# https://www.ssllabs.com/ssltest/ (Qualys SSL Labs)
七、高频故障场景详解
场景 1:证书过期
症状:浏览器红锁、"证书已过期或尚未生效"
排查:
openssl x509 -in cert.pem -dates -noout
notBefore=Jan 1 00:00:00 2024 GMT
notAfter=Jan 1 00:00:00 2025 GMT ← 已过期
修复:
· Let's Encrypt → certbot renew && systemctl reload nginx
· 商业证书 → 重新签发 → 替换文件 → 重载服务
· 检查证书自动续签 cron 是否生效
预防:
· 监控证书剩余天数(<30 天告警)
· 自动化续签 + 重载
场景 2:证书域名不匹配
症状:"NET::ERR_CERT_COMMON_NAME_INVALID"
原因:
· 证书只签了 example.com,用户访问 www.example.com
· 证书 SAN 列表里没有用户访问的域名
验证:
openssl x509 -in cert.pem -ext subjectAltName -noout
修复:
· 重新申请包含正确域名的证书
· 确保 SAN 覆盖所有访问入口(含 www、api、裸域)
场景 3:中间证书缺失
症状:部分客户端报不信任,部分正常
(老 Android / iOS / Java 客户端)
验证:
openssl s_client -connect example.com:443 -showcerts
# 只看到一张证书 = 缺中间 CA
修复:
cat leaf.crt intermediate.crt > fullchain.pem
重新加载 Web 服务
场景 4:TLS 版本 / 密码套件不兼容
症状:老设备/老系统报 "ERR_SSL_VERSION_OR_CIPHER_MISMATCH"
排查:
nmap --script ssl-enum-ciphers -p 443 example.com
原因:
服务端只开 TLS 1.2+,客户端只支持 TLS 1.0
或密码套件无交集
修复(平衡安全与兼容):
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
场景 5:混合内容(Mixed Content)
症状:页面加载了但浏览器地址栏显示"不安全"或锁图标带警告
原因:HTTPS 页面里引用了 HTTP 资源(图片、JS、CSS)
排查:浏览器 F12 → Console → Mixed Content 警告
修复:
· 资源改用 HTTPS
· 或加响应头:Content-Security-Policy: upgrade-insecure-requests
· 或 meta 标签:<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
场景 6:HSTS 导致旧 IP 无法访问
症状:之前能访问,后来换了 IP/服务器,浏览器直接拒绝连接
"ERR_CONNECTION_REFUSED" 且无法跳过
原因:之前服务端设了 HSTS,浏览器记住了"这个域名只走 HTTPS"
新服务器证书还没配好,浏览器拒绝降级
排查:
curl -I https://example.com 看 Strict-Transport-Security 头
修复:
· 新服务器先配好有效证书再切换 DNS
· 或清除浏览器 HSTS 缓存(chrome://net-internals/#hsts)
场景 7:OCSP 装订失败导致首次访问慢
症状:首次访问慢(3-5 秒),刷新后正常
原因:客户端在 TLS 握手时去 CA 验证证书吊销状态(OCSP)
CA 的 OCSP 响应慢或不可达
修复:
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/fullchain.pem;
resolver 8.8.8.8 1.1.1.1 valid=300s;
八、TLS 性能优化
| 优化项 | 作用 | 配置示例(Nginx) |
|---|---|---|
| TLS 1.3 | 1-RTT 握手,更快 | ssl_protocols TLSv1.2 TLSv1.3; |
| 会话复用 | 避免重复握手 | ssl_session_cache shared:SSL:10m; |
| 会话票据 | 无状态会话恢复 | ssl_session_tickets on; |
| OCSP Stapling | 避免客户端去 CA 验证 | ssl_stapling on; |
| HSTS | 强制 HTTPS,减少重定向 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; |
| 证书选择 | ECDSA 比 RSA 计算量小 | 用 ECDSA 证书(P-256 或 Ed25519) |
| 硬件加速 | 卸载加密计算 | ssl_engine + 支持 AES-NI 的 CPU |


