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 数据

握手开销:

二、证书申请

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 证书(显示公司名),就需要买:

公司项目就用了商业 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)。配置按这个思路选:

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 会话缓存,复用握手——同一个客户端的多次请求避免完整握手。

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;

参数:

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

提交 https://hstspreload.org/。

8.3 SSL Labs 评分

测试你的 HTTPS 配置:

https://www.ssllabs.com/ssltest/

我们目标 A+ 评级。需要:

九、常见 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 的全过程。

参考资料