HTTPS 配置全攻略
HTTPS 配置全攻略
写在前面
HTTPS 现在已经是网站标配,但很多生产环境的 HTTPS 配置都是"能跑就行"——TLS 1.0/1.1 没禁、密钥交换算法不安全、HSTS 没开、OCSP stapling 没配。本篇会讲清楚 HTTPS 的每一个关键配置,以及我们从 HTTP 切到 HTTPS 时零故障切换的真实过程。
一、HTTPS 基础
1.1 为什么必须 HTTPS
| 风险 | HTTP | HTTPS |
|---|---|---|
| 中间人窃听 | 明文传输 | TLS 加密 |
| 中间人篡改 | 内容可改 | 篡改会被发现 |
| 身份伪造 | 无法验证 | 证书验证 |
| SEO 权重 | 谷歌降权 | 优先收录 |
| 浏览器提示 | “不安全"红色警告 | 绿色锁标 |
| HTTP/2 | 必须 HTTPS | 可以用 |
1.2 TLS 握手流程
Client Server
| |
|--- ClientHello ------------------->| 支持的协议版本、加密套件、随机数
|<--- ServerHello -------------------| 选定的协议版本、加密套件、随机数
|<--- Certificate -------------------| 服务器证书
|<--- ServerHelloDone ---------------|
| |
|--- Key Exchange (Pre-master) ----->| 用证书公钥加密 pre-master secret
|--- Change Cipher Spec ------------>|
|--- Finished ---------------------->|
|<--- Change Cipher Spec ------------|
|<--- Finished ----------------------|
| |
|--- HTTP Request ------------------>| 加密的 HTTP 数据
|<--- HTTP Response -----------------| 加密的 HTTP 数据
握手开销:
- TLS 1.2 1-RTT(两次往返)
- TLS 1.3 0-RTT(一次往返,更快)
二、证书申请
2.1 Let’s Encrypt 免费证书
Let’s Encrypt 是主流选择,完全免费、自动续期。
安装 acme.sh
curl https://get.acme.sh | sh
acme.sh 是纯 shell 实现的 ACME 客户端,比 certbot 更轻量。
申请证书
# DNS API 方式(推荐,无需启动 webserver)
export Tencent_Cloud_SecretId="xxx"
export Tencent_Cloud_SecretKey="xxx"
acme.sh --issue \
-d api.company.com \
-d "*.api.company.com" \
--dns dns_tencent
支持阿里云、腾讯云、Cloudflare、AWS Route53 等几十种 DNS。
安装证书到 nginx 目录
acme.sh --install-cert -d api.company.com \
--cert-file /etc/nginx/ssl/api.company.com.crt \
--key-file /etc/nginx/ssl/api.company.com.key \
--fullchain-file /etc/nginx/ssl/api.company.com.fullchain.crt \
--reloadcmd "nginx -s reload"
自动续期
acme.sh 安装时已经加了 cron,每天检查一次,过期前自动续期:
0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
2.2 商业证书
如果客户/合规要求 OV/EV 证书(显示公司名),就需要买:
- DigiCert
- GlobalSign
- Sectigo
公司项目就用了商业 OV 证书,显示公司名,客户更信任。
2.3 证书格式转换
| 格式 | 用途 |
|---|---|
| PEM | Nginx/Apache,Base64 编码,-----BEGIN CERTIFICATE----- |
| DER | Java/Windows,二进制 |
| PFX/P12 | IIS,带私钥 |
转换:
# PEM 转 PFX
openssl pkcs12 -export -out cert.pfx -inkey cert.key -in cert.crt -certfile ca.crt
# PFX 转 PEM
openssl pkcs12 -in cert.pfx -out cert.pem -nodes
三、Nginx HTTPS 基础配置
server {
listen 443 ssl http2;
server_name api.company.com;
ssl_certificate /etc/nginx/ssl/api.company.com.fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/api.company.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
proxy_pass http://backend;
}
}
# HTTP 强制跳转 HTTPS
server {
listen 80;
server_name api.company.com;
return 301 https://$host$request_uri;
}
3.1 关键指令详解
ssl_protocols
ssl_protocols TLSv1.2 TLSv1.3;
绝对不要包含 SSLv3 / TLSv1 / TLSv1.1——这些协议有已知漏洞(Poodle、BEAST)。
ssl_ciphers
TLS 1.3 强制使用 AEAD 加密套件(AES-GCM、CHACHA20-POLY1305)。配置按这个思路选:
- 优先 ECDHE(椭圆曲线密钥交换)
- 优先 AES-256-GCM 或 CHACHA20-POLY1305
- 绝不使用 CBC 模式、RC4、DES、3DES
Mozilla 的 ssl_config 生成器是最佳实践:
https://ssl-config.mozilla.org/
ssl_prefer_server_ciphers off
ssl_prefer_server_ciphers off; # TLS 1.3 推荐 off
TLS 1.3 强制客户端优先——客户端选择更安全。TLS 1.2 应该 on。
ssl_session_cache
ssl_session_cache shared:SSL:10m;
TLS 会话缓存,复用握手——同一个客户端的多次请求避免完整握手。
shared:SSL:10m= 10MB 共享内存,大约 40000 个会话- 太大浪费内存,太小命中率低
ssl_session_tickets
ssl_session_tickets off;
会话票证是另一种会话复用机制,但有安全风险(票证可以被重放)。生产推荐 off。
四、TLS 1.3 优化
4.1 为什么 TLS 1.3 更快
TLS 1.2 握手需要 2-RTT(两个网络往返)。
TLS 1.3 把握手压到 1-RTT,连接从发送到第一个字节数据只要 1 个往返。
TLS 1.3 还支持 0-RTT——重连时连第一个往返都省了。
4.2 启用 TLS 1.3
ssl_protocols TLSv1.2 TLSv1.3;
至少要兼容 TLS 1.2——有些老客户端不支持 TLS 1.3。
4.3 0-RTT 风险
ssl_early_data on; # 启用 0-RTT
0-RTT 有重放风险——攻击者可以重放第一个请求。
适用:幂等 GET 请求 不适用:任何会改变状态的请求(POST、PUT、DELETE)
我们 production 关闭了 0-RTT:
ssl_early_data off;
五、HSTS(HTTP Strict Transport Security)
5.1 原理
浏览器首次通过 HTTPS 访问站点,服务端返回:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
浏览器记录这个头,未来一段时间内所有 HTTP 请求自动转 HTTPS——即使攻击者把 HTTPS 降级到 HTTP 也无法成功。
5.2 配置
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
参数:
max-age:浏览器记住的时间(秒)includeSubDomains:对所有子域生效preload:提交到 Chrome 的 preload list(写入浏览器源码)
5.3 preload list
提交到 https://hstspreload.org/,审核通过后会写入 Chrome/Firefox/Safari 源码。
⚠️ 一旦提交就很难撤销——必须保证全站 HTTPS,否则会"锁死”。
六、OCSP Stapling
6.1 OCSP 是什么
OCSP(Online Certificate Status Protocol)用于检查证书是否被吊销。
传统 OCSP:客户端发起 HTTPS 请求时,还会去 CA 的 OCSP 服务器查询证书状态——增加 1 个网络请求,影响性能。
6.2 OCSP Stapling
服务端定期查询 OCSP 并缓存结果,在 TLS 握手中把结果"装订"给客户端——客户端无需自己查询。
6.3 Nginx 配置
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/ca-certificates.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
6.4 验证
openssl s_client -connect api.company.com:443 -status < /dev/null
看到 OCSP Response Status: successful 就是生效了。
七、性能优化
7.1 HTTP/2
listen 443 ssl http2;
HTTP/2 多路复用、头部压缩、二进制分帧,比 HTTP/1.1 性能高 30%+。
⚠️ HTTP/2 必须 HTTPS——浏览器对 HTTP 的 HTTP/2 不友好。
7.2 会话复用
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 4h;
我们生产环境调大到 50MB,4 小时超时,实测会话复用率 80%+。
7.3 0-RTT 数据
前面说过,有重放风险,慎重使用。
7.4 密钥长度
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
dhparam.pem 用于 DHE 密钥交换。生成:
openssl dhparam -out dhparam.pem 4096
生成需要几分钟,4096 位安全但会显著增加握手时间。
TLS 1.3 用 ECDHE,不需要 DHE——可以用 256 位 ECDH。
八、实战:零故障 HTTP 切 HTTPS
8.1 为什么不能直接切
直接 301 跳转 HTTPS,如果新配置有问题(证书过期、配置错误),全站 100% 不可访问——没有 HTTP fallback。
8.2 我们的灰度方案
阶段 1:平行部署(2 周)
# 旧 HTTP server
server {
listen 80;
server_name api.company.com;
# 原有配置不动
}
# 新 HTTPS server(暂不强制跳转)
server {
listen 443 ssl http2;
server_name api.company.com;
# HTTPS 配置
}
观察 HTTPS 流量,确认证书、配置都正确。
阶段 2:HSTS 预热(2 周)
server {
listen 80;
server_name api.company.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.company.com;
# 关键 - 用 max-age=300 而不是 1 年
add_header Strict-Transport-Security "max-age=300" always;
# ...
}
5 分钟的 HSTS——浏览器 5 分钟内自动跳 HTTPS,但出问题 5 分钟后能恢复。
阶段 3:逐步延长 HSTS
# 1 周后
add_header Strict-Transport-Security "max-age=86400" always; # 1 天
# 2 周后
add_header Strict-Transport-Security "max-age=604800" always; # 1 周
# 1 个月后
add_header Strict-Transport-Security "max-age=2592000" always; # 1 个月
# 2 个月后
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
每步观察用户反馈,出问题就回退 max-age。
阶段 4:Preload
8.3 SSL Labs 评分
测试你的 HTTPS 配置:
https://www.ssllabs.com/ssltest/
我们目标 A+ 评级。需要:
- TLS 1.2 / 1.3
- 强加密套件
- HSTS
- OCSP Stapling
- 安全的密钥交换
九、常见 HTTPS 故障
9.1 证书过期
# 检查证书过期时间
openssl x509 -in /etc/nginx/ssl/api.company.com.crt -noout -dates
# notBefore=Mar 15 00:00:00 2026 GMT
# notAfter=Jun 13 12:00:00 2026 GMT
教训:监控证书过期时间,提前 30 天告警。
# Prometheus blackbox_exporter 配置
- name: tls_cert_expiry
rules:
- alert: CertExpiresSoon
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30
labels:
severity: warning
9.2 证书链不完整
浏览器报 NET::ERR_CERT_AUTHORITY_INVALID。
原因:只配置了 leaf 证书,没配置 intermediate 证书。
修复:用 fullchain.crt(包含完整链),不是 cert.crt(只有 leaf)。
ssl_certificate /etc/nginx/ssl/api.company.com.fullchain.crt; # ✓
ssl_certificate /etc/nginx/ssl/api.company.com.crt; # ✗
9.3 SNI 问题
一个 IP 上多个 HTTPS 站点——必须用 SNI。Nginx 默认支持,只要每个 server 块配置不同 server_name 和 ssl_certificate。
老版本 Java(6/7)客户端不支持 SNI,连接第一个证书(可能是别的网站)。一般生产环境可以忽略。
9.4 Mixed Content
页面是 HTTPS,但引用了 HTTP 资源——浏览器拒绝加载。
<!-- NG -->
<img src="http://example.com/logo.png">
<script src="http://cdn.example.com/jquery.js"></script>
<!-- OK -->
<img src="https://example.com/logo.png">
<script src="https://cdn.example.com/jquery.js"></script>
或者:
<!-- 也 OK - 协议相对 URL -->
<img src="//example.com/logo.png">
十、本篇小结
| 主题 | 关键点 |
|---|---|
| TLS 协议 | 只用 TLS 1.2 和 1.3,禁掉老的 |
| 加密套件 | AEAD 优先,ECDHE 密钥交换 |
| HSTS | max-age 逐步延长,最后 preload |
| OCSP Stapling | 必须开,提升 TLS 握手性能 |
| 灰度切换 | 不要一刀切,HSTS 预热 5 分钟 |
| 证书监控 | 提前 30 天告警,避免过期事故 |
下一篇讲 缓存策略与性能优化,把首页 TTFB 从 800ms 压到 60ms 的全过程。