Nginx HTTPS 部署与证书管理
一、HTTPS 基础概念
为什么需要 HTTPS
HTTP 是明文传输,中间人可以窃听、篡改数据。HTTPS = HTTP + TLS 加密,保证:
- 机密性:数据加密,第三方看不到内容
- 完整性:数据不会被篡改
- 身份验证:浏览器能确认网站是真的 demo.test.com,不是钓鱼网站
HTTPS 和 HTTP 的区别
| 对比 | HTTP | HTTPS |
|---|---|---|
| 端口 | 80 | 443 |
| 加密 | 无 | TLS 加密 |
| 证书 | 不需要 | 需要 SSL 证书 |
| 浏览器显示 | 不安全警告 | 地址栏锁图标 |
二、TLS 握手流程
1. 浏览器访问 https://demo.test.com
2. Nginx 收到请求,发现是 443 端口
3. Nginx 把证书(fullchain.pem,含公钥)发给浏览器
4. 浏览器验证证书:
- 这个证书是签发给 demo.test.com 的吗?→ 是
- 证书过期了吗?→ 没有
- 签发机构(Let's Encrypt)可信吗?→ 可信
5. 浏览器自己生成一个随机的对称密钥
6. 浏览器用 Nginx 的公钥加密这个对称密钥,发给 Nginx
7. Nginx 用私钥(privkey.pem)解密,拿到对称密钥
8. 之后双方用这个对称密钥加密所有通信数据
为什么用非对称加密交换密钥,之后用对称加密?
非对称加密很慢,对称加密很快。所以只在握手阶段用非对称加密交换一个密钥,之后全部用对称加密——兼顾安全和性能。
三、证书文件详解
fullchain.pem(证书/公钥)
- 包含:你网站的公钥 + 中间 CA 证书 + 根证书(完整证书链)
- 用途:发给浏览器,让浏览器验证网站身份
- 位置:
/etc/letsencrypt/live/demo.test.com/fullchain.pem
privkey.pem(私钥)
- 包含:你网站的私钥
- 用途:服务器用来解密浏览器发来的对称密钥
- 位置:
/etc/letsencrypt/live/demo.test.com/privkey.pem - 绝对不能泄露! 泄露了等于 HTTPS 形同虚设
cert.pem(网站证书,不含证书链)
- 只包含你网站的证书,不含中间 CA 证书
- 一般用 fullchain.pem 而不是 cert.pem,避免浏览器验证时找不到中间证书
四、Nginx HTTPS 配置(博客的实际配置)
server {
server_name demo.test.com www.demo.test.com;
listen 443 ssl; # 监听 443 端口,启用 SSL
ssl_certificate /etc/letsencrypt/live/demo.test.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/demo.test.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
...
}
server {
listen 80; # HTTP 端口
server_name demo.test.com www.demo.test.com ...;
return 301 https://$server_name$request_uri; # 强制跳转 HTTPS
}
逐行解释
| 指令 | 作用 |
|---|---|
| listen 443 ssl | 监听 443 端口,启用 TLS 加密 |
| ssl_certificate | 证书文件路径(公钥+证书链),发给浏览器验证 |
| ssl_certificate_key | 私钥文件路径,服务器用来解密 |
| include options-ssl-nginx.conf | 引入 certbot 自动生成的安全 SSL 参数 |
| ssl_dhparam | Diffie-Hellman 参数文件,增强密钥交换安全性 |
| listen 80 + return 301 | HTTP 强制跳转 HTTPS |
为什么 listen 80 要 301 跳转?
用户在浏览器输入 http://demo.test.com(注意是 http),请求会走到 listen 80 的 server 块。如果不做跳转,用户就一直停在不安全的 HTTP 上。
return 301 https://$server_name$request_uri; 告诉浏览器:"你该用 https",浏览器收到 301 后自动跳转到 https://demo.test.com。
五、certbot 的 SSL 安全参数
/etc/letsencrypt/options-ssl-nginx.conf 文件内容:
ssl_session_cache shared:le_nginx_SSL:10m; # SSL 会话缓存,10MB 共享内存
ssl_session_timeout 1440m; # 会话超时 24 小时
ssl_session_tickets off; # 关闭 session ticket(更安全)
ssl_protocols TLSv1.2 TLSv1.3; # 只允许 TLS 1.2 和 1.3
ssl_prefer_server_ciphers off; # 不强制服务端选择加密套件
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:..."; # 允许的加密算法
核心要点
- 只允许 TLS 1.2 和 1.3:老的 SSLv3、TLS 1.0/1.1 已经不安全,不支持
- 加密算法用 GCM 模式:目前最安全的对称加密方式
- ssl_session_cache:缓存 SSL 会话,避免每次连接都重新握手(性能优化)
- ssl_session_tickets off:关闭 session ticket 可防止某些攻击,安全性更高
六、Let's Encrypt + certbot 工作机制
Let's Encrypt 是什么
免费的 SSL 证书颁发机构(CA)。以前 SSL 证书要花钱买,Let's Encrypt 让任何人都能免费获得可信证书。
certbot 是什么
自动化的证书管理工具,帮你完成:申请证书 → 配置 Nginx → 自动续期。
申请证书的完整流程
1. 你执行:certbot --nginx -d demo.test.com -d www.demo.test.com
2. certbot 向 Let's Encrypt 证明你拥有这个域名:
- Let's Encrypt 在你的网站根目录放一个验证文件
- Let's Encrypt 访问 http://demo.test.com/.well-known/acme-challenge/xxx
- 如果能访问到 → 证明你控制这个域名
- 验证通过
3. Let's Encrypt 签发证书
4. certbot 自动把证书路径写入 Nginx 配置
5. certbot 自动设置定时续期任务
自动续期机制(systemd timer)
本博客的服务器用的是 systemd timer 方式:
systemctl cat certbot.timer
OnCalendar=*-*-* 00,12:00:00 # 每天 0:00 和 12:00 各检查一次
RandomizedDelaySec=43200 # 随机延迟 0~12 小时(避免所有服务器同时请求)
Persistent=true # 错过执行时间,开机后补执行
ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-renew
# -q 静默模式
# renew 检查所有证书,如果剩余有效期 < 30 天就自动续期
查看证书信息
# 查看证书到期时间
sudo certbot certificates
# 手动测试续期(不实际执行)
sudo certbot renew --dry-run
七、HTTPS 完整请求链路
用户输入 https://demo.test.com
浏览器 → DNS 解析 → shturl.cc/OFB6
→ TCP 三次握手(端口 443)
→ TLS 握手:
Nginx 发送 fullchain.pem
浏览器验证证书
浏览器生成对称密钥,用公钥加密发给 Nginx
Nginx 用 privkey.pem 解密
→ HTTP 请求(对称加密)
Nginx 匹配 location → 转发给后端
→ 返回响应(对称加密)
→ 浏览器渲染页面
八、从零开始:SSL 证书配置实操
实操环境
- 云服务商:UCloud
- 服务器位置:香港(免备案)
- 公网 IP:192.0.2.1
- 域名:example.com
- 服务:OpenClaw(Docker 容器,端口 18789)
- 系统:Ubuntu + Nginx
前置条件:确认 certbot 已安装
# 查看 certbot 是否已安装
ubuntu@server:~$ which certbot && certbot --version
/usr/bin/certbot
certbot 2.9.0
# 如果没安装,执行以下命令:
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
安装说明:
certbot- 核心工具,负责申请和续期证书python3-certbot-nginx- Nginx 插件,能自动修改 Nginx 配置
第一步:删除现有证书(模拟裸机状态)
模拟一台没有 SSL 证书的服务器,从零开始:
ubuntu@server:~$ sudo certbot delete --cert-name example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certificate(s) are selected for deletion:
* example.com
WARNING: Before continuing, ensure that the listed certificates are not being
used by any installed server software (e.g. Apache, nginx, mail servers).
Deleting a certificate that is still being used will cause the server software
to stop working. See https://certbot.org/deleting-certs for information on
deleting certificates safely.
Are you sure you want to delete the above certificate(s)?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y
Deleted all files relating to certificate example.com.
输出解释:
WARNING- 警告:如果证书正在被 Nginx 使用,删除后网站会无法访问(Y)es/(N)o- 确认删除,输入y回车
第二步:准备 Nginx 配置(HTTP 临时配置)
删除证书后,Nginx 配置不能引用已删除的证书文件,需要一个纯 HTTP 的临时配置:
ubuntu@server:~$ cat /etc/nginx/conf.d/example.conf
# 反向代理(HTTP 临时配置)
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:18789;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
重载 Nginx 使配置生效:
ubuntu@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: the configuration file /etc/nginx/nginx.conf test is successful
此时状态: 网站只能通过 http://example.com 访问,显示"不安全"
第三步:申请 SSL 证书
ubuntu@server:~$ sudo certbot --nginx -d example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for example.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2026-10-07.
These files will be updated when the certificate renews.
Deploying certificate
Successfully deployed certificate for example.com to /etc/nginx/conf.d/example.conf
Congratulations! You have successfully enabled HTTPS on https://example.com
certbot 自动做了什么:
- 域名验证:在
http://example.com/.well-known/acme-challenge/放置验证文件 - 申请证书:Let's Encrypt 验证通过后签发证书
- 下载证书:保存到
/etc/letsencrypt/live/example.com/ - 修改 Nginx:自动添加 SSL 配置到 Nginx
- 重载 Nginx:使配置生效
第四步:查看证书状态
ubuntu@server:~$ sudo certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
Certificate Name: example.com
Serial Number: 56bf92f7cc2bc49296c6819f7c681e8e86c
Key Type: ECDSA
Domains: example.com
Expiry Date: 2026-10-07 06:54:18+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
关键信息:
VALID: 89 days- 证书还有 89 天到期Key Type: ECDSA- 使用 ECDSA 加密(比 RSA 更快更安全)
第五步:配置自动续期
5.1 什么是 systemd timer?
systemd timer 是 Linux 的定时任务机制(类似 Windows 的计划任务)。certbot.timer 会定期检查证书是否需要续期。
5.2 查看 timer 配置
ubuntu@server:~$ systemctl cat certbot.timer
# /usr/lib/systemd/system/certbot.timer
[Unit]
Description=Run certbot twice daily
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=43200
Persistent=true
[Install]
WantedBy=timers.target
配置说明:
OnCalendar=*-*-* 00,12:00:00- 每天 0:00 和 12:00 各检查一次RandomizedDelaySec=43200- 随机延迟 0~12 小时(避免所有服务器同时请求 Let's Encrypt)Persistent=true- 如果错过了执行时间(比如关机了),开机后补执行
5.3 检查 timer 是否运行
ubuntu@server:~$ systemctl list-timers | grep certbot
Thu 2026-07-09 18:05:14 HKT 2h 11min Thu 2026-07-09 01:29:18 HKT - certbot.timer certbot.service
ubuntu@server:~$ systemctl is-active certbot.timer
active
5.4 如果 timer 没启用,手动启用
# 启用开机自启
sudo systemctl enable certbot.timer
# 立即启动
sudo systemctl start certbot.timer
# 验证
systemctl is-active certbot.timer
# 输出 active 表示已运行
第六步:测试自动续期
ubuntu@server:~$ sudo certbot renew --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/example.com/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
看到 "Congratulations" 说明自动续期配置成功。
--dry-run 只是模拟测试,不会真的续期证书。
第七步:查看 certbot 修改后的 Nginx 配置
ubuntu@server:~$ cat /etc/nginx/conf.d/example.conf
server {
server_name example.com;
location / {
proxy_pass http://127.0.0.1:18789;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name example.com;
return 404; # managed by Certbot
}
certbot 自动添加了:
listen 443 ssl- 启用 HTTPSssl_certificate- 证书路径ssl_certificate_key- 私钥路径include options-ssl-nginx.conf- 安全参数ssl_dhparam- DH 参数return 301- HTTP 自动跳转 HTTPS
注意: 标有 # managed by Certbot 的行由 certbot 自动管理,不要手动修改。
第八步:重载 Nginx 并测试访问
# 重载 Nginx
ubuntu@server:~$ sudo nginx -t && sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: the configuration file /etc/nginx/nginx.conf test is successful
# 测试 HTTPS 访问
ubuntu@server:~$ curl -I https://example.com
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
看到 HTTP/1.1 200 OK 说明 HTTPS 配置成功。
九、常见问题排查
问题1:certbot 申请证书失败
错误: Challenge failed for domain example.com
原因: 域名没有正确解析到服务器,或 80 端口无法访问
排查步骤:
# 1. 检查域名解析是否指向服务器 IP
nslookup example.com
# 2. 检查 80 端口是否开放
curl -I http://example.com/.well-known/acme-challenge/test
# 3. 检查防火墙
sudo ufw status
问题2:Nginx 启动失败
错误: cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file
原因: Nginx 配置引用了不存在的证书文件
解决:
# 1. 检查配置语法
sudo nginx -t
# 2. 如果证书不存在,先修改配置去掉 SSL 部分
# 3. 然后重新申请证书
sudo certbot --nginx -d example.com
问题3:自动续期失败
查看续期日志:
sudo journalctl -u certbot.service -n 50
常见原因:
- DNS 解析失败(域名指向了错误的 IP)
- 80 端口被防火墙阻挡
- Let's Encrypt 服务器暂时不可用
十、运维最佳实践
1. 备份策略
# 备份证书和 Nginx 配置
sudo tar -czf ssl-backup-$(date +%Y%m%d).tar.gz /etc/letsencrypt/ /etc/nginx/conf.d/
# 备份到远程服务器
scp ssl-backup-*.tar.gz user@remote-server:/backup/
2. 监控证书到期
# 查看证书到期时间
sudo certbot certificates | grep "Expiry Date"
# 写一个脚本检查并告警(可加入 cron 定期执行)
#!/bin/bash
EXPIRY=$(sudo certbot certificates | grep "Expiry Date" | awk '{print $3}')
DAYS=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))
if [ $DAYS -lt 30 ]; then
echo "证书将在 $DAYS 天后过期!"
fi
3. 服务迁移清单
迁移服务器时需要迁移:
/etc/letsencrypt/- 证书文件/etc/nginx/conf.d/- Nginx 配置- 域名 DNS 解析(更新 A 记录指向新 IP)
十一、问答
Q:HTTPS 是怎么工作的?
浏览器发请求 → Nginx 发送证书(公钥) → 浏览器验证证书 → 浏览器生成对称密钥用公钥加密发给服务器 → 服务器用私钥解密 → 之后用对称密钥加密通信。
Q:为什么用非对称加密交换密钥,之后用对称加密?
非对称加密慢,对称加密快。握手阶段用非对称加密交换密钥,之后用对称加密传输数据,兼顾安全和性能。
Q:SSL 证书怎么来的?
Let's Encrypt 免费签发,certbot 工具自动申请。certbot 会验证你拥有这个域名,通过后自动签发证书并配置 Nginx。
Q:证书怎么续期?
certbot 设置了 systemd timer,每天检查两次,如果证书剩余有效期不足 30 天就自动续期。无需手动操作。
Q:listen 80 和 listen 443 的关系?
80 是 HTTP 端口,443 是 HTTPS 端口。listen 80 的 server 块做 301 跳转到 HTTPS,listen 443 ssl 的 server 块处理正式请求。
Q:fullchain.pem 和 privkey.pem 分别是什么?
fullchain.pem 是证书(含公钥和证书链),发给浏览器验证身份。privkey.pem 是私钥,服务器用来解密浏览器发来的对称密钥。私钥绝对不能泄露。
Q:certbot 删除证书后 Nginx 会挂吗?
会的。如果 Nginx 配置引用了已删除的证书,nginx -t 会报错,Nginx 无法启动。需要先修改配置去掉 SSL 部分,或者重新申请证书。
Q:systemd timer 和 cron 有什么区别?
- cron:传统的定时任务,配置简单但功能有限
- systemd timer:更现代,支持错过执行补执行(Persistent=true)、随机延迟等高级功能
Q:为什么需要随机延迟(RandomizedDelaySec)?
如果所有服务器都在同一时刻请求 Let's Encrypt,会造成类似 DDoS 的效果。随机延迟可以让请求分散在不同时间。
Q:证书快到期了但自动续期失败怎么办?
- 查看日志:
sudo journalctl -u certbot.service - 手动续期:
sudo certbot renew - 如果手动也失败,检查域名解析和 80 端口是否正常
- 紧急情况:重新申请证书
sudo certbot --nginx -d example.com
说明:文中域名、IP均为教学演示虚拟地址,无真实线上服务
