HTTPS 已经是 Web 的默认传输方式。当流量经过 CDN 时,TLS 加密不再是”客户端到源站”的点对点连接,而是分成了两段——客户端到边缘、边缘到源站。这两段的 TLS 配置、证书管理和安全策略各有不同的工程挑战。
一、CDN HTTPS 架构模式
CDN 的 HTTPS 部署有三种基本模式,选择取决于安全需求和运维复杂度:
1.1 边缘终止(Edge Termination)
最常见的模式:TLS 在边缘 PoP 终止,边缘到源站可以使用 HTTP 或独立的 TLS 连接:
模式 A:边缘 HTTPS + 回源 HTTP(最简单,安全性最低)
客户端 ──[HTTPS]──→ CDN 边缘 ──[HTTP]──→ 源站
加密 明文
模式 B:边缘 HTTPS + 回源 HTTPS(推荐)
客户端 ──[HTTPS]──→ CDN 边缘 ──[HTTPS]──→ 源站
TLS 1 TLS 2
CDN 证书 源站证书
模式 C:Full Strict(最严格)
客户端 ──[HTTPS]──→ CDN 边缘 ──[HTTPS + 证书验证]──→ 源站
TLS 1 TLS 2 + 验证源站证书有效性
Cloudflare 的 SSL 模式对比:
| 模式 | 客户端→CDN | CDN→源站 | 源站证书要求 | 安全级别 |
|---|---|---|---|---|
| Off | HTTP | HTTP | 无 | 无加密 |
| Flexible | HTTPS | HTTP | 无 | 半加密 |
| Full | HTTPS | HTTPS | 自签名即可 | 加密但不验证 |
| Full (Strict) | HTTPS | HTTPS | 有效 CA 签发 | 完全加密 |
工程建议:生产环境至少使用 Full 模式,推荐 Full (Strict)。Flexible 模式存在中间人攻击风险——CDN 到源站的明文传输可以被截获。
1.2 TLS 透传(TLS Passthrough)
CDN 不终止 TLS,将加密流量直接转发到源站:
TLS 透传模式:
客户端 ──[HTTPS]────────────────────→ 源站
└── 经过 CDN 但不解密 ──┘
CDN 行为:
- 通过 SNI(Server Name Indication)确定目标域名
- 基于 SNI 路由到正确的源站
- 不解密流量 → 不能缓存 → 不能修改内容
- 不能注入安全头 → 不能做 WAF
透传模式的 Nginx 配置:
stream {
# 基于 SNI 路由的 TLS 透传
map $ssl_preread_server_name $backend {
api.example.com api_backend;
www.example.com web_backend;
default default_backend;
}
upstream api_backend {
server origin-api.example.com:443;
}
upstream web_backend {
server origin-web.example.com:443;
}
upstream default_backend {
server origin-default.example.com:443;
}
server {
listen 443;
# 预读 SNI 但不终止 TLS
ssl_preread on;
proxy_pass $backend;
proxy_connect_timeout 5s;
# 透传模式下保留客户端 IP
# 需要 PROXY Protocol 支持
proxy_protocol on;
}
}
1.3 双证书架构
CDN 使用自己的证书面向客户端,用另一套证书连接源站:
双证书架构:
客户端 ──[TLS: CDN 证书]──→ CDN 边缘 ──[TLS: 源站证书]──→ 源站
CDN 证书(前端):
- 由 CDN 托管或用户上传
- 域名: www.example.com
- 签发 CA: Let's Encrypt / DigiCert
源站证书(后端):
- 由源站部署
- 可以使用 CDN 签发的 Origin CA 证书
- 也可以使用公共 CA 证书
优势:
- 证书轮换互不影响
- CDN 可以优化前端 TLS 配置(新协议/密码套件)
- 源站证书可以使用 CDN 的 Origin CA(免费,15 年有效期)
Cloudflare Origin CA 证书的使用:
# 在源站部署 Cloudflare Origin CA 证书
# 1. 通过 Cloudflare API 生成 Origin CA 证书
curl -X POST "https://api.cloudflare.com/client/v4/certificates" \
-H "X-Auth-Key: $CF_API_KEY" \
-H "X-Auth-Email: admin@example.com" \
-H "Content-Type: application/json" \
-d '{
"hostnames": ["example.com", "*.example.com"],
"requested_validity": 5475,
"request_type": "origin-rsa",
"csr": "'"$(cat server.csr)"'"
}'
# 2. 保存证书
# 将返回的 certificate 保存为 origin-cert.pem
# 3. 源站 Nginx 配置
cat > /etc/nginx/conf.d/origin-tls.conf << 'CONF'
server {
listen 443 ssl;
server_name example.com;
# Cloudflare Origin CA 证书
ssl_certificate /etc/nginx/ssl/origin-cert.pem;
ssl_certificate_key /etc/nginx/ssl/origin-key.pem;
# 只允许 Cloudflare IP 访问(安全加固)
# https://www.cloudflare.com/ips/
allow 173.245.48.0/20;
allow 103.21.244.0/22;
allow 103.22.200.0/22;
allow 103.31.4.0/22;
allow 141.101.64.0/18;
allow 108.162.192.0/18;
allow 190.93.240.0/20;
allow 188.114.96.0/20;
allow 197.234.240.0/22;
allow 198.41.128.0/17;
deny all;
location / {
proxy_pass http://localhost:8080;
}
}
CONF二、证书管理工程
CDN 环境下的证书管理比单服务器复杂得多——需要在全球数百个边缘节点上同步部署证书。
2.1 证书类型选择
CDN 证书类型对比:
┌──────────────┬───────────────┬──────────────┬─────────────┐
│ │ 共享证书 │ 专用证书 │ 自定义证书 │
├──────────────┼───────────────┼──────────────┼─────────────┤
│ 域名 │ CDN 子域名 │ 自有域名 │ 自有域名 │
│ 示例 │ xxx.cdn.com │ www.example.com│ www.example.com│
│ 谁签发 │ CDN 提供商 │ CDN 代签 │ 用户自行签发 │
│ 费用 │ 免费 │ 免费/付费 │ 用户自费 │
│ SAN 共享 │ 和其他客户共享 │ 独占 │ 独占 │
│ 管理复杂度 │ 无 │ 低 │ 高 │
│ 私钥控制 │ CDN 持有 │ CDN 持有 │ 用户上传 │
│ 适用场景 │ 测试/原型 │ 大多数生产 │ 合规要求高 │
└──────────────┴───────────────┴──────────────┴─────────────┘
2.2 自动化证书管理
使用 ACME 协议实现 CDN 证书的自动签发和续期:
# cdn_cert_manager.py — CDN 证书自动化管理
import json
import time
import subprocess
from datetime import datetime, timedelta
class CDNCertManager:
"""管理 CDN 边缘节点的证书生命周期"""
def __init__(self, cdn_api_url, api_token):
self.cdn_api_url = cdn_api_url
self.api_token = api_token
self.cert_store = {} # domain -> cert_info
def check_expiry(self):
"""检查所有证书的到期时间"""
expiring_soon = []
for domain, info in self.cert_store.items():
days_left = (info['not_after'] - datetime.utcnow()).days
if days_left <= 30:
expiring_soon.append({
'domain': domain,
'days_left': days_left,
'serial': info['serial'],
'issuer': info['issuer'],
})
if days_left <= 0:
print(f"[CRITICAL] {domain} 证书已过期!")
elif days_left <= 7:
print(f"[WARNING] {domain} 证书将在 {days_left} 天后过期")
else:
print(f"[INFO] {domain} 证书将在 {days_left} 天后过期")
return expiring_soon
def renew_certificate(self, domain):
"""使用 ACME 续期证书"""
print(f"Renewing certificate for {domain}...")
# 使用 certbot 签发证书(DNS-01 验证)
result = subprocess.run([
'certbot', 'certonly',
'--dns-cloudflare',
'--dns-cloudflare-credentials', '/etc/letsencrypt/cloudflare.ini',
'-d', domain,
'-d', f'*.{domain}',
'--preferred-challenges', 'dns-01',
'--non-interactive',
'--agree-tos',
], capture_output=True, text=True)
if result.returncode != 0:
raise Exception(f"Certificate renewal failed: {result.stderr}")
# 读取新证书
cert_path = f'/etc/letsencrypt/live/{domain}/fullchain.pem'
key_path = f'/etc/letsencrypt/live/{domain}/privkey.pem'
with open(cert_path) as f:
cert_pem = f.read()
with open(key_path) as f:
key_pem = f.read()
return cert_pem, key_pem
def deploy_to_cdn(self, domain, cert_pem, key_pem):
"""将证书部署到 CDN 边缘节点"""
import requests
response = requests.put(
f"{self.cdn_api_url}/certificates/{domain}",
headers={
'Authorization': f'Bearer {self.api_token}',
'Content-Type': 'application/json',
},
json={
'certificate': cert_pem,
'private_key': key_pem,
}
)
if response.status_code != 200:
raise Exception(f"CDN deployment failed: {response.text}")
result = response.json()
print(f"Certificate deployed to {result['edge_nodes_count']} edge nodes")
print(f"Propagation time: ~{result['estimated_propagation_seconds']}s")
return result
def verify_deployment(self, domain, expected_serial):
"""验证证书在边缘节点是否生效"""
import ssl
import socket
# 测试多个 PoP 节点
test_resolvers = {
'US-East': '1.1.1.1',
'EU-West': '1.0.0.1',
'AP-East': '8.8.8.8',
}
results = {}
for region, resolver in test_resolvers.items():
try:
context = ssl.create_default_context()
with socket.create_connection((domain, 443), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=domain) as ssock:
cert = ssock.getpeercert()
serial = cert.get('serialNumber', '')
not_after = cert.get('notAfter', '')
results[region] = {
'serial_match': serial == expected_serial,
'not_after': not_after,
'version': ssock.version(),
}
except Exception as e:
results[region] = {'error': str(e)}
return results2.3 通配符证书与多域名证书
通配符证书 vs SAN 证书的选择:
通配符证书(Wildcard):
*.example.com
├── 覆盖: api.example.com, www.example.com, cdn.example.com
├── 不覆盖: example.com(裸域名需要单独列入 SAN)
├── 不覆盖: sub.api.example.com(不支持多级子域名)
└── 适用: 子域名数量多且动态变化
SAN 证书(Subject Alternative Name):
SAN: example.com, www.example.com, api.example.com
├── 精确列出所有域名
├── 可以包含不同根域名
├── Let's Encrypt 限制: 每张证书最多 100 个 SAN
└── 适用: 域名固定且数量有限
CDN 大规模场景:
通常使用通配符 + SAN 组合:
CN: example.com
SAN: example.com, *.example.com, *.cdn.example.com
2.4 证书监控与告警
#!/bin/bash
# cert-monitor.sh — 批量检查 CDN 证书到期时间
DOMAINS=(
"www.example.com"
"api.example.com"
"cdn.example.com"
"static.example.com"
)
ALERT_DAYS=30
CRITICAL_DAYS=7
check_cert() {
local domain=$1
# 获取证书信息
cert_info=$(echo | openssl s_client -servername "$domain" \
-connect "$domain:443" 2>/dev/null | \
openssl x509 -noout -dates -subject -issuer -serial 2>/dev/null)
if [ $? -ne 0 ]; then
echo "[ERROR] $domain: 无法获取证书"
return 1
fi
# 解析到期时间
not_after=$(echo "$cert_info" | grep "notAfter" | cut -d= -f2)
expiry_epoch=$(date -d "$not_after" +%s 2>/dev/null)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
# 解析证书信息
issuer=$(echo "$cert_info" | grep "issuer" | sed 's/issuer=//')
serial=$(echo "$cert_info" | grep "serial" | cut -d= -f2)
# 检查 TLS 版本
tls_version=$(echo | openssl s_client -servername "$domain" \
-connect "$domain:443" 2>/dev/null | grep "Protocol" | awk '{print $3}')
# 输出结果
if [ $days_left -le 0 ]; then
echo "[CRITICAL] $domain: 证书已过期 ${days_left#-} 天"
elif [ $days_left -le $CRITICAL_DAYS ]; then
echo "[CRITICAL] $domain: 证书将在 $days_left 天后过期"
elif [ $days_left -le $ALERT_DAYS ]; then
echo "[WARNING] $domain: 证书将在 $days_left 天后过期"
else
echo "[OK] $domain: 证书有效期剩余 $days_left 天 (TLS $tls_version)"
fi
echo " 签发者: $issuer"
echo " 到期日: $not_after"
echo " 序列号: $serial"
}
echo "=== CDN Certificate Monitor ==="
echo "Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo ""
for domain in "${DOMAINS[@]}"; do
check_cert "$domain"
echo ""
donePrometheus 证书到期监控:
# prometheus/rules/cert-alerts.yml
groups:
- name: certificate_alerts
rules:
- alert: CertificateExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 30 * 24 * 3600
for: 1h
labels:
severity: warning
annotations:
summary: "证书即将过期: {{ $labels.instance }}"
description: "{{ $labels.instance }} 的 TLS 证书将在 {{ $value | humanizeDuration }} 后过期"
- alert: CertificateExpiryCritical
expr: probe_ssl_earliest_cert_expiry - time() < 7 * 24 * 3600
for: 10m
labels:
severity: critical
annotations:
summary: "证书即将过期(紧急): {{ $labels.instance }}"
description: "{{ $labels.instance }} 的 TLS 证书将在 {{ $value | humanizeDuration }} 后过期,请立即续期"三、边缘 TLS 优化
CDN 边缘节点的 TLS 配置直接影响握手延迟和安全性。
3.1 TLS 版本与密码套件
# CDN 边缘节点 TLS 配置(Nginx)
ssl_protocols TLSv1.2 TLSv1.3;
# TLS 1.3 密码套件(优先使用)
# 注意:TLS 1.3 密码套件不通过 ssl_ciphers 配置
# Nginx 自动使用 TLS 1.3 的三个密码套件:
# TLS_AES_256_GCM_SHA384
# TLS_CHACHA20_POLY1305_SHA256
# TLS_AES_128_GCM_SHA256
# TLS 1.2 密码套件(按优先级排列)
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 on;
# ECDHE 曲线配置
ssl_ecdh_curve X25519:secp384r1:secp256r1;
密码套件选择的工程依据:
性能对比(TLS 1.2 握手,单次):
密码套件 | 握手耗时 | 安全等级
─────────────────────────────────────────────────────
ECDHE-ECDSA-AES128-GCM-SHA256 | 0.8ms | 高
ECDHE-RSA-AES128-GCM-SHA256 | 1.2ms | 高
ECDHE-ECDSA-CHACHA20-POLY1305 | 0.9ms | 高(移动端优)
ECDHE-RSA-AES256-GCM-SHA384 | 1.5ms | 很高
DHE-RSA-AES256-GCM-SHA384 | 15ms | 高(但太慢)
注意:
- ECDSA 比 RSA 快 40%+(密钥更短,签名更快)
- CHACHA20 在没有 AES-NI 的 ARM 设备上比 AES 快 3 倍
- 不应使用 DHE(慢且无额外安全收益)
- 不应使用 CBC 模式(有 padding oracle 攻击风险)
3.2 OCSP Stapling
OCSP(Online Certificate Status Protocol)用于检查证书是否被吊销。不使用 OCSP Stapling 时,浏览器需要额外请求 OCSP 服务器:
不使用 OCSP Stapling:
客户端 ──[TLS 握手]──→ CDN 边缘
客户端 ──[OCSP 查询]──→ OCSP 服务器(额外 RTT)
等待 OCSP 响应... (50-300ms)
客户端 ──[继续握手]──→ CDN 边缘
使用 OCSP Stapling:
CDN 边缘预先获取 OCSP 响应并缓存
客户端 ──[TLS 握手]──→ CDN 边缘(附带 OCSP 响应)
无需额外查询
节省 50-300ms
Nginx OCSP Stapling 配置:
# 启用 OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# 信任链证书(用于验证 OCSP 响应)
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
# DNS 解析器(用于查询 OCSP 服务器)
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
验证 OCSP Stapling 是否工作:
# 检查 OCSP Stapling 状态
echo | openssl s_client -servername www.example.com \
-connect www.example.com:443 -status 2>/dev/null | \
grep -A 5 "OCSP Response Status"
# 正常输出:
# OCSP Response Status: successful (0x0)
# OCSP Response Data:
# OCSP Response Status: successful (0x0)
# Cert Status: good
# This Update: Jul 18 00:00:00 2025 GMT
# 如果输出 "OCSP response: no response sent"
# 说明 Stapling 未生效,需要检查:
# 1. ssl_trusted_certificate 路径是否正确
# 2. DNS 解析器是否可达
# 3. OCSP 服务器是否响应3.3 会话恢复(Session Resumption)
减少 TLS 握手的 RTT 开销:
TLS 1.2 Session Ticket:
首次握手(Full Handshake):2 RTT
客户端 ──→ ClientHello
←── ServerHello + Certificate + ServerHelloDone
客户端 ──→ ClientKeyExchange + ChangeCipherSpec + Finished
←── NewSessionTicket + ChangeCipherSpec + Finished
后续握手(Abbreviated):1 RTT
客户端 ──→ ClientHello(含 Session Ticket)
←── ServerHello + ChangeCipherSpec + Finished
客户端 ──→ ChangeCipherSpec + Finished
TLS 1.3 PSK:
首次握手:1 RTT
后续握手(PSK):1 RTT(减少计算量)
0-RTT 恢复:0 RTT(但有重放风险)
CDN 环境下的 Session Ticket 挑战——多个边缘节点需要共享 Ticket Key:
# Session Ticket 配置
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
# Session Ticket Key(所有边缘节点必须使用相同的 key)
ssl_session_ticket_key /etc/nginx/ssl/ticket-current.key;
ssl_session_ticket_key /etc/nginx/ssl/ticket-previous.key;
# Key 轮换脚本(每 12 小时轮换一次)
# 0 */12 * * * /etc/nginx/ssl/rotate-ticket-key.sh
Session Ticket Key 轮换脚本:
#!/bin/bash
# rotate-ticket-key.sh — TLS Session Ticket Key 轮换
KEY_DIR="/etc/nginx/ssl"
CURRENT_KEY="$KEY_DIR/ticket-current.key"
PREVIOUS_KEY="$KEY_DIR/ticket-previous.key"
# 保留旧 key 用于解密已有 ticket
cp "$CURRENT_KEY" "$PREVIOUS_KEY"
# 生成新 key(48 字节)
openssl rand 48 > "$CURRENT_KEY"
chmod 600 "$CURRENT_KEY" "$PREVIOUS_KEY"
# 分发到所有边缘节点
for node in $(cat /etc/nginx/edge-nodes.txt); do
scp "$CURRENT_KEY" "$PREVIOUS_KEY" "$node:$KEY_DIR/"
ssh "$node" "nginx -s reload"
done
echo "Ticket key rotated at $(date)"3.4 双证书部署(RSA + ECDSA)
为了兼容旧客户端(RSA)同时获得更好性能(ECDSA),可以在边缘部署双证书:
server {
listen 443 ssl http2;
server_name www.example.com;
# RSA 证书(兼容性)
ssl_certificate /etc/nginx/ssl/rsa/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/rsa/privkey.pem;
# ECDSA 证书(性能)
ssl_certificate /etc/nginx/ssl/ecdsa/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/ecdsa/privkey.pem;
# Nginx 会根据客户端支持的密码套件自动选择证书:
# 支持 ECDSA 的客户端 → 使用 ECDSA 证书
# 不支持 ECDSA 的客户端 → 降级使用 RSA 证书
}
性能对比:
# RSA 2048 签名性能
$ openssl speed rsa2048
sign verify sign/s verify/s
rsa 2048 bits 0.000932s 0.000029s 1073.0 34892.2
# ECDSA P-256 签名性能
$ openssl speed ecdsap256
sign verify sign/s verify/s
256 bits ecdsa (nistp256) 0.000034s 0.000098s 29387.5 10255.0
# ECDSA 签名速度是 RSA 的 27 倍
# RSA 验证速度是 ECDSA 的 3.4 倍
# TLS 握手中服务端做签名(ECDSA 优势),客户端做验证(RSA 优势)
# 综合考虑:ECDSA 在 TLS 场景更优(签名是瓶颈)四、回源 HTTPS 策略
CDN 到源站的 HTTPS 连接同样需要精心配置。
4.1 回源证书验证
回源验证级别:
Level 0: 不验证源站证书(不推荐)
- CDN → 源站: HTTPS 但不验证证书
- 风险: 中间人攻击可以冒充源站
Level 1: 验证证书有效性(推荐)
- CDN → 源站: HTTPS + 验证证书链 + 验证域名
- 源站需要有效的 CA 签发证书
Level 2: 证书钉扎(高安全场景)
- CDN → 源站: HTTPS + 验证证书指纹
- 即使 CA 被入侵也不受影响
Nginx 回源证书验证配置:
upstream origin {
server origin.example.com:443;
}
server {
location / {
proxy_pass https://origin;
proxy_ssl_server_name on;
proxy_ssl_name origin.example.com;
# 启用证书验证
proxy_ssl_verify on;
proxy_ssl_verify_depth 2;
proxy_ssl_trusted_certificate /etc/nginx/ssl/ca-certificates.crt;
# 指定回源使用的 TLS 版本
proxy_ssl_protocols TLSv1.2 TLSv1.3;
proxy_ssl_ciphers HIGH:!aNULL:!MD5;
# 可选:客户端证书认证(mTLS 回源)
# proxy_ssl_certificate /etc/nginx/ssl/client.crt;
# proxy_ssl_certificate_key /etc/nginx/ssl/client.key;
}
}
4.2 回源 mTLS
在高安全场景下,CDN 到源站使用双向 TLS 认证:
mTLS 回源架构:
CDN 边缘 ──[mTLS]──→ 源站
CDN 出示:
- CDN 客户端证书(证明请求来自 CDN,不是攻击者直连)
源站验证:
- CDN 客户端证书的有效性
- 证书中的 CN/SAN 是否匹配 CDN 提供商
配合 IP 白名单实现双重验证:
1. 源站防火墙只放行 CDN IP 段
2. 源站 TLS 只接受 CDN 的客户端证书
→ 攻击者无法绕过 CDN 直接访问源站
源站 Nginx 配置 mTLS:
server {
listen 443 ssl;
server_name origin.example.com;
# 源站自身证书
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 要求客户端证书(mTLS)
ssl_client_certificate /etc/nginx/ssl/cdn-ca.crt; # CDN CA 证书
ssl_verify_client on;
ssl_verify_depth 2;
# 只在证书验证通过后处理请求
location / {
# 将客户端证书信息传递给后端
proxy_set_header X-SSL-Client-CN $ssl_client_s_dn_cn;
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_pass http://localhost:8080;
}
}
五、CDN HTTPS 安全最佳实践
5.1 安全头部配置
CDN 边缘可以统一注入安全头部,避免在每个源站重复配置:
# CDN 边缘统一注入安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Content-Security-Policy(需要根据实际情况调整)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: cdn.example.com;" always;
5.2 HSTS Preload
将域名加入浏览器的 HSTS Preload List,确保从第一次访问就使用 HTTPS:
HSTS Preload 的要求:
1. 提供有效的 TLS 证书
2. 同一 IP 上 HTTP 重定向到 HTTPS
3. 所有子域名都支持 HTTPS
4. HSTS 头包含:
- max-age >= 31536000(1 年)
- includeSubDomains
- preload
注意事项:
- 加入 Preload List 后很难撤回(需要等浏览器更新周期)
- 确保所有子域名(包括内部系统)都支持 HTTPS
- 建议先用短 max-age 测试,确认无问题后再提交
5.3 证书透明度(CT)
Certificate Transparency 帮助检测未授权的证书签发:
# 查询 CT 日志中你的域名的所有证书
# 使用 crt.sh(CT 日志聚合器)
curl -s "https://crt.sh/?q=example.com&output=json" | \
python3 -c "
import json, sys
certs = json.load(sys.stdin)
for c in sorted(certs, key=lambda x: x['not_before'], reverse=True)[:10]:
print(f\"签发日期: {c['not_before']} 到期: {c['not_after']}\")
print(f\"签发者: {c['issuer_name']}\")
print(f\"域名: {c['common_name']}\")
print()
"
# 监控是否有未授权的证书签发
# 如果发现不是你签发的证书 → 可能是 CA 被入侵或 DNS 劫持5.4 ECH(Encrypted Client Hello)
TLS 握手中的 SNI(Server Name Indication)以明文传输,暴露了用户访问的域名。ECH 对 ClientHello 中的 SNI 加密:
传统 TLS 握手(SNI 明文):
ClientHello:
SNI: www.example.com ← 网络中间设备可见
Supported Versions: TLS 1.3
Key Share: X25519
ECH(加密 ClientHello):
ClientHello:
ECH: <加密的内部 ClientHello> ← SNI 被加密
Outer SNI: cloudflare-ech.com ← 只显示 CDN 域名
CDN 边缘解密后:
Inner ClientHello:
SNI: www.example.com ← 只有 CDN 能看到
CDN 是 ECH 的天然部署平台——所有使用同一 CDN 的域名共享外层 SNI,使得网络审查者无法区分不同域名的流量。
六、故障排查
6.1 常见 HTTPS 问题诊断
# 1. 证书链不完整
$ curl -v https://www.example.com 2>&1 | grep -E "(SSL|certificate)"
# 错误: SSL certificate problem: unable to get local issuer certificate
# 原因: CDN 未部署中间证书
# 修复: 上传 fullchain.pem(包含中间证书)
# 2. 证书域名不匹配
$ echo | openssl s_client -servername www.example.com \
-connect www.example.com:443 2>/dev/null | \
openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# 如果 SAN 中没有 www.example.com → 证书域名不匹配
# 3. 混合内容(Mixed Content)
# HTTPS 页面加载 HTTP 资源 → 浏览器阻止
# 排查方法:
curl -s https://www.example.com | grep -i "http://"
# 4. HSTS 导致无法回退 HTTP
# 如果错误配置了 HSTS,用户无法通过 HTTP 访问
# 浏览器清除方法:
# Chrome: chrome://net-internals/#hsts → Delete domain
# Firefox: 清除历史记录 → 站点设置
# 5. TLS 版本不兼容
$ openssl s_client -servername www.example.com \
-connect www.example.com:443 \
-tls1_2 2>&1 | head -5
# 如果失败 → CDN 可能禁用了 TLS 1.2
# 影响: 旧设备(Android 4.x, IE 10)无法访问6.2 端到端 TLS 诊断脚本
#!/bin/bash
# cdn-tls-audit.sh — CDN HTTPS 完整诊断
DOMAIN="${1:-www.example.com}"
echo "=== CDN TLS Audit: $DOMAIN ==="
echo ""
# 1. 证书信息
echo "--- Certificate Info ---"
echo | openssl s_client -servername "$DOMAIN" \
-connect "$DOMAIN:443" 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates -serial
echo ""
# 2. TLS 版本
echo "--- TLS Version ---"
for ver in tls1 tls1_1 tls1_2 tls1_3; do
result=$(echo | openssl s_client -servername "$DOMAIN" \
-connect "$DOMAIN:443" -"$ver" 2>&1 | grep "Protocol")
if [ $? -eq 0 ]; then
echo " $ver: SUPPORTED ($result)"
else
echo " $ver: NOT SUPPORTED"
fi
done
echo ""
# 3. OCSP Stapling
echo "--- OCSP Stapling ---"
echo | openssl s_client -servername "$DOMAIN" \
-connect "$DOMAIN:443" -status 2>/dev/null | \
grep "OCSP Response Status"
echo ""
# 4. 安全头部
echo "--- Security Headers ---"
headers=$(curl -sI "https://$DOMAIN" 2>/dev/null)
for header in "Strict-Transport-Security" "X-Content-Type-Options" \
"X-Frame-Options" "Content-Security-Policy" "Referrer-Policy"; do
value=$(echo "$headers" | grep -i "$header" | head -1)
if [ -n "$value" ]; then
echo " ✓ $value"
else
echo " ✗ $header: MISSING"
fi
done
echo ""
# 5. 证书链验证
echo "--- Certificate Chain ---"
echo | openssl s_client -servername "$DOMAIN" \
-connect "$DOMAIN:443" -showcerts 2>/dev/null | \
grep -E "s:|i:" | head -10
echo ""
echo "Audit complete."七、总结
CDN HTTPS 工程的核心要点:
- 架构选择:生产环境使用 Full (Strict) 模式——边缘 TLS 终止 + 回源 HTTPS + 证书验证
- 证书管理:自动化是关键——ACME 自动签发 + 到期监控 + 告警
- TLS 优化:ECDSA 双证书、OCSP Stapling、Session Resumption——每一项都能减少几十毫秒
- 回源安全:IP 白名单 + mTLS 双重验证,防止绕过 CDN 直连源站
- 安全头部:在边缘统一注入 HSTS、CSP 等安全头,确保一致性
上一篇: 动态加速:TCP 优化、路由优化与边缘计算 下一篇: CDN 故障调试:缓存命中率、回源异常与头分析
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
网络工程索引
汇总本站网络工程系列文章,覆盖分层模型、以太网、IP、TCP、DNS、TLS、HTTP/2/3、CDN、BGP 与故障诊断。
【网络工程】证书工程:PKI 体系、ACME 与自动化管理
证书过期宕机是最常见也最可避免的生产事故。本文从工程角度剖析 X.509 证书结构、PKI 信任链的工作原理、ACME 协议与 Let's Encrypt 的自动化部署、私有 CA 的搭建实践,以及 cert-manager 在 Kubernetes 中的证书管理方案。覆盖证书监控、轮换策略与过期告警的完整工程体系。
【网络工程】mTLS 工程实践:服务间双向认证
mTLS(双向 TLS)在微服务架构中实现服务间的身份认证和通信加密。本文从工程角度剖析 mTLS 的握手流程差异、证书分发的三种模式、SPIFFE/SPIRE 的标准化身份框架,以及 Istio 和 Linkerd 中 mTLS 的实现原理。覆盖性能开销测量、调试方法和大规模部署的证书轮换策略。
【网络工程】CDN 架构原理:PoP、边缘节点与 Origin Shield
系统解剖 CDN 的多层缓存架构——从 DNS 调度到 PoP 内部结构、Origin Shield 回源保护、多 CDN 部署策略。结合实际配置和响应头分析,给出 CDN 架构的工程理解。