3.11 SSL/TLS 证书的获取、配置与续期
预计阅读时间:16 分钟
📖 目录
学习目标
学完本章后,你将能够:
- 理解 SSL/TLS 证书的作用和 HTTPS 的工作原理
- 使用 Certbot 申请和自动续期 Let's Encrypt 证书
- 在 Nginx 中正确配置 HTTPS
- 使用
openssl生成自签名证书和查看证书信息 - 配置安全的 TLS 协议和加密套件
核心知识
- SSL/TLS——传输层安全协议,加密客户端和服务器之间的通信,防止窃听和篡改
- HTTPS——HTTP over TLS,即经过 TLS 加密的 HTTP 通信
- 证书(Certificate)——证明网站身份的数字文档,由 CA 签名
- CA(证书颁发机构)——签发和管理数字证书的受信任第三方
- Let's Encrypt——提供免费 DV 类型证书的 CA,证书有效期 90 天
- Certbot——EFF 开发的 Let's Encrypt 客户端,自动申请、部署和续期证书
- ACME 协议——自动证书管理环境协议,Certbot 通过它向 Let's Encrypt 申请证书
- 自签名证书(Self-Signed Certificate)——自己签名的证书,适合测试环境,浏览器不信任
知识关联
- 前置知识:3.4:Nginx Web服务器 Nginx Web 服务器、3.3:实战:搭建个人网站 搭建个人网站
- 后续影响:HTTPS 是 5.1:Linux 安全加固 Linux 安全加固和 5.7:HAProxy 负载均衡 HAProxy 负载均衡的基础
- 配套技术:Nginx、Apache、HAProxy 等 Web 服务器/反向代理
原理讲解
HTTPS 握手过程
当浏览器访问 HTTPS 网站时,发生 TLS 握手:① 客户端发送支持的 TLS 版本和加密套件列表 → ② 服务器返回证书(含公钥)和选择的加密套件 → ③ 客户端验证证书合法性(是否由受信任 CA 签发、是否过期、域名是否匹配)→ ④ 客户端生成对称密钥,用服务器公钥加密后发送 → ⑤ 服务器用私钥解密获得对称密钥 → ⑥ 后续通信用对称密钥加密。
证书类型
| 类型 | 验证级别 | 适用场景 |
|---|---|---|
| DV(域名验证) | 仅验证域名控制权 | 个人网站、博客(Let's Encrypt 免费提供) |
| OV(组织验证) | 验证域名+企业身份 | 企业官网、电商 |
| EV(扩展验证) | 最严格审核 | 银行、金融机构(浏览器地址栏显示企业名) |
TLS 1.2 与 TLS 1.3 握手对比
握手的核心目标始终是两件事:身份认证(确认服务器持有证书对应的私钥)+ 密钥协商(安全地商定对称密钥)。TLS 1.3 把完整握手从 2 个往返压缩到 1 个往返,速度提升明显:
| 对比项 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 完整握手往返 | 2-RTT | 1-RTT(首包即携带密钥协商参数) |
| 会话恢复 | Session ID / Session Ticket | PSK + 0-RTT(恢复连接零往返,首包即可发数据) |
| 加密套件 | 几十种组合,容易配置出弱套件 | 仅 5 种 AEAD 套件,废弃 RC4、CBC、压缩 |
| 密钥交换 | RSA / ECDHE 等 | 强制前向保密(仅 ECDHE),废弃 RSA 密钥交换 |
| 摘要算法 | 可配置 SHA-1 / SHA-256 | 统一 SHA-256 / SHA-384 |
| 兼容性 | 所有客户端 | 2017 年后发布的浏览器均支持,极老设备不支持 |
0-RTT 恢复虽然快,但存在重放攻击风险——支付、登录等敏感请求不应使用 0-RTT。运维视角的结论:只开启 TLSv1.2 + TLSv1.3。TLS 1.0/1.1 已在 2020 年被主流浏览器废弃,不要为了极少数老设备牺牲所有用户的安全。
证书信任链:根证书、中间证书与叶子证书
浏览器不会直接信任你的证书,而是沿着"叶子证书 → 中间证书 → 根证书"的链条逐级验证,最终在系统内置的根证书库(信任锚)中找到签发者。三层结构的意义在于根证书离线路由——如果根证书私钥泄露,只需吊销中间证书,不必更换全球设备里的根证书库。
| 层级 | 签发者 | 有效期 | 说明 |
|---|---|---|---|
| 根证书(Root CA) | 自签 | 10-25 年 | 预装在浏览器/系统中,私钥离线保存,几乎不直接签发网站证书 |
| 中间证书(Intermediate) | 根证书 | 3-10 年 | 由根 CA 签发,负责签发各类证书(Let's Encrypt 用 ISRG Root X1 等交叉签名) |
| 叶子证书(Leaf) | 中间证书 | 90 天-1 年 | 你的网站证书,包含域名、公钥、有效期等 |
# 查看线上证书链:每段 s: 是一级证书,i: 是它的签发者
echo | openssl s_client -connect example.com:443 -showcerts 2>/dev/null | grep -E "s:|i:"
# 输出示例:
# s:C = US, O = Let's Encrypt, CN = R10 ← 中间证书
# i:C = US, O = Internet Security Research Group, CN = ISRG Root X1 ← 根证书
# s:CN = example.com ← 叶子证书
# i:C = US, O = Let's Encrypt, CN = R10
# 本地验证证书链完整性
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt \
-untrusted /etc/letsencrypt/live/example.com/chain.pem \
/etc/letsencrypt/live/example.com/cert.pem
# 输出: cert.pem: OK
部署时务必使用 fullchain.pem(叶子 + 中间证书合并)。如果只配置 cert.pem,服务器不会自动补发中间证书,部分严格校验的客户端(如移动 App)会验证失败。
为什么 TLS 需要证书链而不是单一证书?
证书链的设计是出于安全隔离的考虑。根 CA 的私钥是最敏感的资产——如果它泄露,攻击者可以签发任何域名的证书,全球的信任体系崩溃。因此根证书的私钥被离线保存(放在物理隔离的保险柜里),日常签发工作由中间 CA 完成。如果中间 CA 被攻破,只需吊销该中间证书,根证书和全球设备上的信任库不受影响。
这种分层信任模型也支持灵活的交叉签名——Let's Encrypt 的根证书 ISRG Root X1 在 2015 年才被主流浏览器内置,早期它通过交叉签名借助 DST Root CA X3 获得信任。证书链让新 CA 可以平滑过渡,而不需要等待所有设备更新根证书库。
为什么推荐 Let's Encrypt 而不是自签名证书?
| 维度 | Let's Encrypt | 自签名证书 |
|---|---|---|
| 浏览器信任 | 所有主流浏览器自动信任 | 浏览器显示"不安全"警告 |
| 费用 | 免费 | 免费(但无信任) |
| 自动化 | ACME 协议全自动申请/续期 | 需手动生成和替换 |
| 有效期 | 90 天(自动续期) | 可自定义(通常 1-10 年) |
| 适用场景 | 生产环境公网网站 | 内部测试环境、开发调试 |
自签名证书适合开发和测试环境,但生产环境必须使用受信任 CA 签发的证书。Let's Encrypt 的免费 + 自动化彻底改变了 HTTPS 的普及率——2015 年全球 HTTPS 网站不到 30%,现在已超过 90%。
示例代码
1. 使用 Certbot 获取 Let's Encrypt 证书
# 安装 Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
# 方式一:自动配置 Nginx(推荐)
sudo certbot --nginx -d example.com -d www.example.com
# 该命令自动:创建证书 → 修改 Nginx 配置(添加 HTTPS、HTTP→HTTPS 跳转)
# 方式二:只获取证书,手动配置
sudo certbot certonly --nginx -d example.com
# 方式三:standalone 模式(临时占用 80 端口验证)
sudo systemctl stop nginx
sudo certbot certonly --standalone -d example.com
sudo systemctl start nginx
# 方式四:通配符证书(需 DNS 验证)
sudo certbot certonly --manual --preferred-challenges dns -d "*.example.com"
# 查看证书信息
sudo certbot certificates
# 输出:
# Found the following certs:
# Certificate Name: example.com
# Domains: example.com www.example.com
# Expiry Date: 2026-10-28 11:07:00+00:00 (VALID: 90 days)
# Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
2. 证书文件结构
/etc/letsencrypt/live/example.com/
├── fullchain.pem # 完整证书链(推荐用于 Nginx ssl_certificate)
├── privkey.pem # 私钥(Nginx ssl_certificate_key,权限 600)
├── cert.pem # 仅服务器证书(不含中间证书)
└── chain.pem # 中间证书链
3. Nginx HTTPS 配置(安全标准)
server {
listen 443 ssl;
http2 on; # nginx 1.25.1+ 推荐写法(listen 443 ssl http2 已弃用)
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 仅允许安全协议
ssl_protocols TLSv1.2 TLSv1.3;
# 优先使用现代加密套件
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# 会话缓存(提高性能)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
root /var/www/example;
index index.html;
}
# HTTP → HTTPS 301 跳转
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
4. 证书续期与管理
# Certbot 自动续期定时器
systemctl status certbot.timer
# 输出: ● certbot.timer - Run certbot twice daily
# Trigger: 2026-07-31 06:07:28 (11h left)
# 手动测试续期
sudo certbot renew --dry-run
# 续期后执行自定义操作(如重载 Nginx)
sudo certbot renew --deploy-hook "systemctl reload nginx"
# 吊销证书
sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem
5. 自签名证书(测试环境)
# 生成自签名证书(有效期 365 天)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout key.pem -out cert.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=Dev/CN=localhost"
# 查看证书详情
openssl x509 -in cert.pem -text -noout | head -20
# 查看远程服务器的证书
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -subject -dates
# 转换证书格式(PEM → PFX)
openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem
6. HTTP-01 与 DNS-01 验证方式对比
Let's Encrypt 用"证明你控制域名"来签发证书,ACME 协议支持两种验证方式:
| 对比项 | HTTP-01 | DNS-01 |
|---|---|---|
| 验证原理 | 在 http://域名/.well-known/acme-challenge/<token> 放置指定内容 | 在 DNS 添加 _acme-challenge.域名 的 TXT 记录 |
| 通配符支持 | 不支持 *.example.com | 支持通配符证书 |
| 前提条件 | 80 端口可访问,域名已解析到本机 | 能操作 DNS 面板或使用 DNS 服务商 API |
| 自动化难度 | certbot 全自动(推荐首选) | 需要 DNS 插件或手动步骤 |
| 签发速度 | 秒级 | 依赖 DNS 生效,通常 1-5 分钟 |
# HTTP-01(自动、最常用):要求 80 端口通
sudo certbot certonly --nginx -d example.com -d www.example.com
# DNS-01 手动流程(通配符证书必须用 DNS-01)
sudo certbot certonly --manual --preferred-challenges dns \
-d "*.example.com" -d example.com
# 按提示去 DNS 面板添加 TXT 记录(验证值 30 秒内有效,注意复制):
# 输出: Please deploy a DNS TXT record under the name:
# _acme-challenge.example.com with the following value:
# abcdefghijklmnopqrstuvwxyz123456
# 添加完成并生效后按回车,等待验证通过
# DNS-01 全自动(以 Cloudflare 为例,需先安装 certbot-dns-cloudflare)
# /etc/letsencrypt/cloudflare.ini(chmod 600)
# dns_cloudflare_api_token = 你的_API_Token
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials \
/etc/letsencrypt/cloudflare.ini -d "*.example.com" -d example.com
7. 证书续期自动化:systemd timer 详解
Let's Encrypt 证书只有 90 天,手动续期不现实。Debian/Ubuntu 安装 certbot 后自带 certbot.timer:每天检查两次,证书剩余不足 30 天才真正续期(避免频繁请求触发 CA 限流)。
# 确认定时器已启用
sudo systemctl list-timers | grep certbot
# 输出: NEXT LEFT LAST UNIT
# Fri 2026-07-31 06:07:28 CST 11h Thu 2026-07-30 18:07:26 CST certbot.timer
# 自定义续期钩子:续期成功后自动重载 Nginx
# /etc/systemd/system/certbot.service.d/override.conf:
# [Service]
# ExecStart=
# ExecStart=/usr/bin/certbot -q renew --deploy-hook "systemctl reload nginx"
sudo systemctl daemon-reload
# 手动测试续期(不会真正替换证书,只走完整流程)
sudo certbot renew --dry-run
# 输出: Congratulations, all simulated renewals succeeded
# 查看最近一次续期日志
sudo journalctl -u certbot.service -n 20
# 输出: Certificate not yet due for renewal; no action taken.
8. 多域名 SAN 证书与 OCSP Stapling
一张证书可以同时保护多个域名(SAN 扩展)。证书签发后无法追加域名,只能重新签发。OCSP Stapling 让 Nginx 主动从 CA 拉取证书吊销状态并缓存,随握手一起发给客户端——省去客户端逐个查询 OCSP 服务器的往返,既提速又提升隐私。
# 一张 SAN 证书覆盖多个域名 + 子域
sudo certbot --nginx -d example.com -d www.example.com -d api.example.com
# 查看证书包含的域名
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
-noout -text | grep -A1 "Subject Alternative Name"
# 输出: X509v3 Subject Alternative Name:
# DNS:example.com, DNS:www.example.com, DNS:api.example.com
# Nginx 完整配置:多域名 + OCSP Stapling + HSTS
server {
listen 443 ssl;
http2 on; # nginx 1.25.1+ 推荐写法(listen 443 ssl http2 已弃用)
server_name example.com www.example.com api.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_stapling on; # 开启 OCSP Stapling
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s; # 用于解析 OCSP 响应者域名
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}
# 验证 OCSP Stapling 是否生效
echo | openssl s_client -connect example.com:443 -status 2>/dev/null | grep "OCSP Response Status"
# 输出: OCSP Response Status: successful
9. 证书过期监控与告警
自动续期不是 100% 可靠:DNS 服务商 API 失效、服务器宕机错过续期窗口、端口被防火墙挡掉都会导致续期失败。必须有独立的过期监控兜底,不能只依赖 certbot 自己。
#!/bin/bash
# /usr/local/bin/check_cert.sh — 证书过期检查(配合 cron 每天执行)
CERT="/etc/letsencrypt/live/example.com/fullchain.pem"
WARN_DAYS=14
# 提取到期时间并换算为剩余天数
EXPIRY=$(openssl x509 -in "$CERT" -noout -enddate | cut -d= -f2)
REMAIN=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))
if [ "$REMAIN" -le 0 ]; then
echo "ALERT: 证书已过期 $(( -REMAIN )) 天!"
elif [ "$REMAIN" -le "$WARN_DAYS" ]; then
echo "WARN: 证书剩余 $REMAIN 天,请检查自动续期"
# 告警通知(以 Slack Webhook 为例)
# curl -fsS -X POST -H "Content-Type: application/json" \
# -d '{"text":"证书 '$CERT' 剩余 '$REMAIN' 天"}' \
# https://hooks.slack.com/services/你的_Webhook
else
echo "OK: 证书剩余 $REMAIN 天"
fi
# crontab 每天 9 点检查,异常时发送邮件(与 ch26 监控方案联动)
0 9 * * * /usr/local/bin/check_cert.sh | grep -E "ALERT|WARN" | mail -s "证书告警" ops@example.com
监控其他服务器上的域名同理:echo | openssl s_client -connect www.example.com:443 2>/dev/null | openssl x509 -noout -enddate,把各服务器结果汇总到一个监控平台统一告警。
常见错误
| 错误表现 | 根因 | 正确做法 |
|---|---|---|
| 浏览器显示"您的连接不是私密连接" | 证书未受信任(自签名/过期/域名不匹配) | 确认证书是否来自受信任 CA、域名是否匹配、证书是否过期 |
| certbot 提示"Domain not found" | 域名未正确解析到服务器 IP | dig example.com 确认 A 记录指向本机 |
| HTTPS 配置后 Nginx 启动失败 | 证书文件路径错误或私钥权限不当 | sudo openssl rsa -in privkey.pem -check 验证私钥;确认文件 600 权限 |
| 使用自签名证书时 curl 报错 | curl 默认验证服务器证书 | 测试时加 -k 参数忽略验证:curl -k https://localhost |
| Let's Encrypt 证书到期未续期 | certbot.timer 未运行或续期失败 | systemctl status certbot.timer 检查;certbot renew 手动续期并查日志 |
最佳实践
| 实践 | 原理 | 示例 |
|---|---|---|
| 仅启用 TLSv1.2 和 TLSv1.3 | 旧版 TLS/SSL 有已知安全漏洞 | ssl_protocols TLSv1.2 TLSv1.3; |
| 定期检查证书到期时间 | 监控过期避免服务不可用 | 用到< 30 天时发送告警:openssl s_client ... | openssl x509 -noout -enddate |
| 配置 HSTS | 防止 SSL Stripping 攻击 | add_header Strict-Transport-Security "max-age=63072000" always; |
| 私钥文件权限设为 600 | 防止其他用户读取私钥 | sudo chmod 600 /etc/letsencrypt/live/*/privkey.pem |
| 使用完整的证书链 | 部分旧客户端不包含中间证书,可能导致验证失败 | Nginx 用 fullchain.pem 而非 cert.pem |
练习题
- (概念)TLS 握手过程中,客户端为什么需要用服务器公钥加密对称密钥?为什么要用对称密钥而非公钥加密后续通信?
- (概念)DV、OV、EV 三种证书等级有什么不同?Let's Encrypt 提供的是哪种?它免费的原因是什么?
- (实操)生成本地自签名证书,在 Nginx 中配置 HTTPS 站点。用
curl -k验证可访问后,尝试不指定-k参数观察报错。然后用浏览器访问观察安全提示。 - (实操)如果有域名和云服务器,使用 Certbot 获取真实 Let's Encrypt 证书,配置完整的 Nginx HTTPS 站点(含 HTTP→HTTPS 跳转、HSTS、TLSv1.2/1.3 限制),用 https://www.ssllabs.com/ssltest/ 或
openssl s_client验证配置。 - (🔍 挑战)搭建一个 HTTP 网站,使用 Man-in-the-Middle 思路理解为什么 HTTPS 必要:在同一个网络中用 Wireshark 或 tcpdump 抓取 HTTP 和 HTTPS 请求的包,对比能否看到明文内容。
点击查看答案
- (概念)用服务器公钥加密对称密钥,确保只有持有对应私钥的服务器能解密——实现安全的密钥交换。后续通信使用对称密钥(如 AES),因为对称加密比非对称加密快 100-1000 倍,适合大量数据传输。
- (概念)DV(域名验证)仅验证域名控制权,最低成本;OV(组织验证)额外验证组织真实性;EV(扩展验证)最严格,浏览器地址栏显示组织名称。Let's Encrypt 提供 DV 证书。它免费的原因是通过自动化(ACME 协议)消除了人工验证成本。
- (实操)生成自签名证书:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/key.pem -out /etc/nginx/ssl/cert.pem。Nginx 配置:ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem;。验证:curl -k https://localhost正常,不加-k报SSL certificate problem: self-signed certificate。浏览器会显示"不安全"。 - (实操)
sudo certbot --nginx -d example.com -d www.example.com自动完成。Nginx 配置硬化的关键行:ssl_protocols TLSv1.2 TLSv1.3;、add_header Strict-Transport-Security "max-age=63072000" always;。验证:curl -I https://example.com检查Strict-Transport-Security头是否存在。 - (🔍 挑战)步骤:① 在同一网络的两台机器上部署 HTTP 和 HTTPS 网站;② 在"攻击者"机器上用
sudo tcpdump -i any port 80 -X抓 HTTP 包,可以看到 POST 数据中的明文密码;③ 用tcpdump -i any port 443 -X抓 HTTPS 包,只能看到加密乱码。关键理解:不加密的网络中任何中间节点都可看到明文流量。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释 SSL/TLS 证书的工作原理和证书链结构 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成证书申请、安装、验证、续期 | 在终端实际执行 |
| 原理掌握 | 能说出 HTTPS 握手过程和证书验证机制 | 画出流程图 |
| 故障排查 | 能独立排查证书过期、证书链不完整、混合内容问题 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么需要配置 HSTS 和 OCSP Stapling | 对比不同方案 |
本章总结
HTTPS 已从"加分项"变成"必需品"。Let's Encrypt + Certbot 的组合让免费自动化证书成为现实:一条 certbot --nginx 命令即可完成证书申请和 Nginx 配置。理解证书文件结构(fullchain.pem + privkey.pem)、安全协议配置(TLSv1.2+、HSTS)和自动续期机制是运维 HTTPS 网站的基本功。自签名证书在测试环境够用,但生产必须使用受信任 CA 签发的证书。
速查表
| 命令/操作 | 用途 |
|---|---|
sudo certbot --nginx -d example.com | 自动申请证书并配置 Nginx |
sudo certbot certonly --standalone -d ex.com | 仅获取证书(standalone 模式) |
sudo certbot renew --dry-run | 测试证书续期 |
sudo certbot certificates | 查看所有证书信息 |
openssl x509 -in cert.pem -text -noout | 查看证书详情 |
echo | openssl s_client -connect ex.com:443 | 测试远程服务器 TLS 连接 |
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout key.pem -out cert.pem | 生成自签名证书 |
/etc/letsencrypt/live/example.com/fullchain.pem | Nginx 使用此文件做 ssl_certificate |
/etc/letsencrypt/live/example.com/privkey.pem | Nginx 使用此文件做 ssl_certificate_key |
学习路径建议
- 学完本章后阅读 3.4:Nginx Web服务器 Nginx Web 服务器获取更完整的配置知识
- 进阶可看 5.1:Linux 安全加固 Linux 安全加固了解更多安全配置