跳到主要内容

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

提交:hstspreload.org

单向操作:上了 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: "&#125;&#125; $labels.instance &#125;&#125; 证书 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. 延伸阅读