HTTPS 已经上了,为什么还能更快更安全:从 TLS 1.3 说起
很多个人站长的 HTTPS 是"能跑就行":装了个 Let's Encrypt 证书,Nginx 里写了两行 ssl_certificate,浏览器小锁一亮,就以为万事大吉。但他们不知道的是,Nginx 的默认 TLS 配置里藏着一批二十年前的老古董密码套件——RC4、3DES、CBC 模式的 AES,这些算法今天要么被证明有理论缺陷,要么性能极差。你的站点每天都在用它们和一部分浏览器握手,白白慢了几十毫秒,还留了安全隐患。
这篇文章不堆砌密码学名词,只讲三件事:为什么该开 TLS 1.3、怎么配一套既快又安全的密码套件、以及如何用命令行亲手验证协商结果有没有达到你的预期。做完之后,同样的服务器和带宽,你的 HTTPS 握手会更快,安全评分也会实实在在提高。
TLS 1.3 到底快在哪:少一次往返就少一个 RTT
要理解 TLS 1.3 的价值,得先知道 TLS 1.2 在干什么。一次完整的 TLS 1.2 握手,客户端和服务器之间要来回"发言"两轮,也就是两个 RTT(Round Trip Time,往返时延)。在跨机房访问时,一个 RTT 可能就是 50 到 200 毫秒。两个 RTT 意味着,在你看到第一个字节之前,光握手就花掉了上百毫秒。
TLS 1.3 把这件事压到了一个 RTT:客户端第一条消息就把密钥协商所需的信息全带上,服务器回一条就能开始加密通信。更激进的是 TLS 1.3 的 0-RTT 模式——对已访问过的站点,客户端可以在第一条消息里直接携带加密的应用数据,握手和数据同去,理论上是零额外往返。当然 0-RTT 有重放攻击的顾虑,一般只对幂等的 GET 请求开启,这是后话。
TLS 1.3 还顺手砍掉了所有"有名有姓"的老旧算法:RSA 密钥交换、CBC 分组模式、RC4、SHA-1 全部移除,只保留 AEAD 类现代加密(AES-GCM、ChaCha20-Poly1305)。这意味着安全性和性能这一次是站在同一边的——你不需要在快和稳之间做取舍。
第一步:打开 TLS 1.3 并写一套现代密码套件
先确认你的 Nginx 和 OpenSSL 支持 1.3:
nginx -V 2>&1 | tr ' ' '\n' | grep -E 'nginx|openssl'
# nginx/1.24.0 与 OpenSSL 1.1.1 及以上才支持 TLS 1.3
openssl version
# 需要 1.1.1 或 3.x然后在 server 块的 HTTPS 配置里加上这几行。注意 TLS 1.3 的密码套件不能用 ssl_ciphers 设置,那是给 1.2 用的;1.3 的套件由 ssl_conf_command Ciphersuites 控制:
ssl_protocols TLSv1.2 TLSv1.3;
# 只保留现代 AEAD 套件,顺序即优先级(越前越优先)
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
# 显式指定 TLS 1.3 的套件(顺序即偏好)
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
# 会话复用,避免每次连接都完整握手
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;几个细节值得解释:
ssl_prefer_server_ciphers off;——现代做法是让客户端优先。手机的 AES 硬件加速可能不如服务器的,让客户端挑它最快的算法,整体体验更好。ssl_session_tickets off;——会话票据虽然能加速重连,但在没有轮换密钥的情况下泄露票据密钥会危及前向安全。对个人站来说,用共享内存会话缓存就够了,关掉票据更稳妥。- 把 ECDSA 证书放前面、RSA 放后面,是因为 ECDSA 签名更短更快。如果你只有 RSA 证书,去掉 ECDSA 那几条即可,配置里留不存在的套件不会报错,但会造成期望落差,最好保持和实际证书一致。
第二步:别忘了 OCSP Stapling 和 HSTS
光配密码套件只是完成了一半。真正顺滑的 HTTPS 体验还需要两个配角:
# OCSP Stapling:服务器替客户端去 CA 查吊销状态,少一次外部请求
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem; # 注意:必须包含中间证书
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# HSTS:告诉浏览器以后只用 HTTPS 访问
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;ssl_trusted_certificate 指向的文件必须包含完整的证书链(站点证书 + 中间证书)。用 Let's Encrypt 的话,fullchain.pem 或 chain.pem 都行,别只给站点证书,否则 OCSP 验证会静默失败,stapling 等于没开。
HSTS 加 max-age=31536000 是一年,先别急着加 preload,那需要去浏览器厂商的预加载列表登记,一旦登记错了想撤非常麻烦。先用一年长效期跑稳定了再说。
第三步:动手验证协商结果
配置对不对,不靠猜,用 openssl 命令行直接问服务器。下面这组命令能给出确定答案:
# 1. 看默认协商到哪个协议版本、用了哪个套件
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null \
| grep -E 'Protocol|Cipher'
# 期望看到:Protocol: TLSv1.3,Cipher 以 TLS_AES_ 或 TLS_CHACHA20_ 开头
# 2. 强制用 TLS 1.2 连接,确认老协议下也不再出现 CBC/RC4
openssl s_client -connect www.example.com:443 -tls1_2 </dev/null 2>/dev/null \
| grep Cipher
# 期望:ECDHE-...-GCM 或 CHACHA20,绝不能出现 CBC、RC4、3DES
# 3. 确认 TLS 1.0/1.1 已经彻底拒绝(应连接失败)
openssl s_client -connect www.example.com:443 -tls1_1 </dev/null 2>/dev/null | grep -i 'alert\|error'
# 4. 查看证书链是否完整(确认 OCSP stapling 状态)
echo | openssl s_client -connect www.example.com:443 -servername www.example.com -status 2>/dev/null \
| grep -A2 'OCSP Response'第 1 条如果看不到 TLSv1.3,八成是 ssl_protocols 没加 TLSv1.3,或者 OpenSSL 版本太老。第 2 条只要冒出 CBC 或 RC4,说明你的 ssl_ciphers 没生效,可能被其他 location 里的配置覆盖了——Nginx 里 ssl_ciphers 是继承的,子块若定义了自己的,会覆盖父块,排查时 nginx -T 看最终生效配置最准。第 4 条能看到 OCSP Response Status: successful 才算 stapling 真的工作。
证书链:HTTPS 里最容易被忽视的一环
TLS 配置做得再漂亮,如果证书链不完整,一样会出事。浏览器打开时有时会报 NET::ERR_CERT_AUTHORITY_INVALID,或者"有人打得开、有人打不开"——这十有八九不是密码套件的问题,而是中间证书没配全。
根证书内置在操作系统和浏览器里,但连接站点证书和根证书之间的"中间证书",必须由服务器在握手时一并发送,否则客户端就得自己去 CA 服务器下载,而某些环境(内网、部分安卓系统、某些 HTTP 客户端)不会做这件事,直接判定证书无效。用 openssl 一条命令就能验证链是否完整:
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer
# 看 Verify return code,0 表示链完整可信
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>&1 \
| grep -E 'Verify return code|subject=|issuer='Verify return code: 0 (ok) 才是健康的。如果返回非 0(比如 20 或 21),说明链断了。修复方法:证书文件用完整的 fullchain.pem(站点证书 + 中间证书拼接),而不是只用 cert.pem。用 Let's Encrypt 时,certbot 生成的就是 fullchain.pem,直接指向它即可;用 acme.sh 时,注意选对 --fullchain-file 输出路径。这条链一旦断了,OCSP stapling 也会连带失效,是两个问题一起解决的关键点。
一次 HTTPS 加固的完整核查清单
把这篇文章的要点收拢成一份可以直接逐条打勾的清单,配置完照着核一遍:
- 协议版本:
ssl_protocols TLSv1.2 TLSv1.3;,彻底禁用 SSLv3/TLSv1.0/TLSv1.1。 - 密码套件:
ssl_ciphers只留 ECDHE + GCM/CHACHA20,杀掉 CBC、RC4、3DES。 - TLS 1.3 套件:用
ssl_conf_command Ciphersuites显式指定,别依赖默认。 - 证书链:指向
fullchain.pem,用openssl s_client确认 Verify return code 为 0。 - OCSP Stapling:开
ssl_stapling并配好ssl_trusted_certificate(含中间证书)和resolver。 - HSTS:
Strict-Transport-Security设一年,子域齐全后再考虑 includeSubDomains。 - 会话复用:
ssl_session_cache shared:SSL:10m,ssl_session_tickets off。 - HTTP 跳转:80 端口用
return 301 https://$host$request_uri;全量导到 443,别留明文入口。 - 最终验收:跑一遍 Qualys SSL Labs,目标 A 或 A+,同时用浏览器开发者工具看协议栏是否显示 h3/HTTP2 与 TLS 1.3。
照着这份清单走一遍,你的 HTTPS 就从"能用"升级到"又快又稳"。对搜索引擎和访客来说,一个安全的绿色小锁配合流畅的加载,就是品牌信任最直接的体现。
常见坑:配了反而出问题的几种情况
- 只保留 TLS 1.3 导致老设备打不开。 有些 Android 7 以下的系统浏览器只支持到 TLS 1.2。建议
ssl_protocols TLSv1.2 TLSv1.3;,把 1.2 留着,配合现代套件一样安全。 - OCSP 没配 resolver 或 resolver 被墙。 Nginx 解析 CA 的 OCSP 地址要走 DNS,没配
resolver则 stapling 无法工作;用国内 DNS 解析境外 OCSP 地址慢或失败时,换223.5.5.5试试。 - HSTS 上了却发现某个子域名只有 HTTP。
includeSubDomains会让所有子域强制 HTTPS,如果你的图床子域还没证书,加了之后该子域直接打不开。上线前遍历一遍所有子域。 - 证书换了但 ssl_trusted_certificate 路径没更新。 acme.sh 或 certbot 续期后文件名一般不变,但如果你换过证书签发方式,链文件路径会变,OCSP 就悄悄失效了。
一次配置,长期收益
TLS 配置属于典型的"配一次吃三年"的基础设施。花费半小时把协议版本、密码套件、OCSP、HSTS 四件事做对,换来的是:更快的握手(体感首屏更快)、更高的 SSL 检测评分、以及对未来多年已知攻击的免疫。对个人站长来说,这比折腾花哨的前端特效划算得多——毕竟速度和安全,是搜索排名和用户信任都直接看重的硬指标。改完记得用上面的 openssl 命令逐条验证,再去 Qualys SSL Labs 跑一次评分,确认每个环节都落到实处,而不是停留在配置文件里看着好看。