HTTPS-HSTS与证书管理
1. HTTPS 强制
所有 HTTP 请求 301 跳 HTTPS:
server {
listen 80;
server_name example.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
必须 301 不能 302:HSTS preload 要求 301。
2. HSTS
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
| 字段 | 含义 |
|---|---|
max-age=N | 浏览器记住 N 秒只走 HTTPS |
includeSubDomains | 含所有子域 |
preload | 申请加入浏览器源码列表 |
2.1 推进 HSTS 三阶段
1. max-age=300(5 分钟) ← 灰度验证
2. max-age=86400(1 天) ← 短期
3. max-age=31536000 ← 1 年
4. + includeSubDomains ← 含子域
5. + preload + 申请 hstspreload.org ← 锁死
每步观察一段时间,没问题再加大。
2.2 HSTS preload
申请前提:
- 主域有效 HTTPS
- HTTP 301(不能 302)跳 HTTPS
- 包含
max-age >= 31536000; includeSubDomains; preload - 所有子域都支持 HTTPS
单向操作:上了 preload 后撤销极慢(几个月)。所有子域必须永久 HTTPS。
3. 证书选型
| 类型 | 适用 | 价格 |
|---|---|---|
| Let's Encrypt DV | 大多数业务 | 免费 |
| 阿里云 / 腾讯免费 DV | 国内 | 免费 |
| OV / EV | 企业 / 金融 | 几百 - 几千/年 |
| 通配符 | 多子域 | 中等 |
| 多域名(SAN) | SaaS | 中等 |
3.1 Let's Encrypt + certbot
sudo certbot --nginx -d example.com -d www.example.com
# 自动续期已配(cron / systemd timer)
sudo certbot renew --dry-run
90 天有效期,自动续期。
3.2 通配符(DNS-01)
sudo certbot certonly --manual --preferred-challenges dns \
-d "*.example.com" -d "example.com"
DNS 厂商插件自动化:
sudo apt install python3-certbot-dns-cloudflare
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d "*.example.com"
4. K8s cert-manager
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
ingressClassName: nginx
- dns01:
cloudflare:
apiTokenSecretRef:
name: cf-token
key: api-token
selector:
dnsZones: ["example.com"]
Ingress 加注解自动签发:
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts: [app.example.com]
secretName: app-tls
5. 证书过期监控
最常见生产事故。
5.1 Prometheus blackbox_exporter
- alert: SSLCertExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 30 * 86400
for: 1h
labels: { severity: warning }
annotations:
summary: "}} $labels.instance }} 证书 30 天内过期"
- alert: SSLCertExpiredCritical
expr: probe_ssl_earliest_cert_expiry - time() < 7 * 86400
for: 1h
labels: { severity: critical }
5.2 命令行检查
# 看域名证书过期时间
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate
# notAfter=Jun 18 12:00:00 2027 GMT
# 脚本批量检查
for d in example.com api.example.com cdn.example.com; do
expiry=$(echo | openssl s_client -connect $d:443 -servername $d 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
echo "$d: $expiry"
done
6. 私钥保护
- 权限
chmod 600,所有者 root 或服务账户 - 永远不进 git
- 泄露立即吊销 + 重签
- K8s 用 Secret + etcd 加密
- 集中管理用 Vault
7. CAA 记录
DNS 加 CAA 限制谁可签证书:
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 iodef "mailto:security@example.com"
防止恶意 CA 给你的域签证书。
8. Certificate Transparency
CT 日志全公开。监控有没有人冒签你的域名:
9. 全站 HTTPS 检查清单
- 所有域名 HTTPS
- HTTP 301 → HTTPS(不是 302)
- HSTS 生效(先短期再长期)
- 证书链完整(fullchain.pem 而非 cert.pem)
- TLS 1.2 + 1.3,禁用 1.0/1.1
- Mozilla SSL Generator 配置 Intermediate / Modern
- OCSP Stapling 开启
- CAA 记录
- 证书过期监控
- 自动续期
- 私钥权限 600
10. 常见反模式
- HTTP 302 跳 HTTPS:HSTS preload 不通过
- HSTS 直接 max-age=63072000 + preload:未验证就锁死
- 证书人工续期:忘一次全站挂
- 私钥进 git:泄露
cert.pem而非fullchain.pem:iOS / Android 部分客户端报错- 没 CAA:第三方 CA 可签
- 不监控过期:等用户报警才知
11. 延伸阅读
- Mozilla SSL Configuration Generator
- Let's Encrypt 文档
- SSL Labs 测评
- crt.sh
- 模块 02 TLS 握手与证书体系
- 模块 03 HTTPS 配置与 HTTP/2 优化