欢迎光临
我们一直在努力

HTTPS证书这事儿,远不止申请完就完事了

HTTPS证书这事儿,远不止申请完就完事了

摘要:申请HTTPS证书听着简单——Let’s Encrypt免费申请,Nginx配一下就完事。但在多台服务器、CDN回源、自动续期这些场景下,坑不少。本文从一次证书过期导致全站挂掉的真实事故出发,聊聊证书管理的完整链路。

关键词:HTTPS、SSL证书、Let’s Encrypt、证书续期、Nginx

分类:运维 / 网络


周六早上的一通电话

周六上午十点,客户打电话过来说网站打不开了。浏览器报"连接不安全"。

我以为是服务器挂了,SSH上去一看,一切正常。Nginx在跑,应用在跑,CPU内存都好好的。

curl -I https://客户域名/

curl: (60) SSL certificate problem: certificate has expired

证书过期了。

echo | openssl s_client -connect 客户域名:443 -servername 客户域名 2>/dev/null | openssl x509 -noout -dates

notBefore=May 10 00:00:00 2025 GMT
notAfter=Aug 8 23:59:59 2025 GMT

8月8号到期,今天9号。过了一天了。

客户用的Let’s Encrypt免费证书,开着自动续期。按理说不应该过期。查了一下日志:

cat /var/log/letsencrypt/letsencrypt.log | tail -50

Failed to renew certificate: DNS problem: NXDOMAIN looking up A for www.example.com

续期的时候DNS解析失败了。

原因挺简单:客户上周换了DNS服务商,旧的DNS已经停了,新的还没完全生效。certbot恰好在那个窗口期尝试续期,DNS查不到A记录,失败了。之后没有重试,证书就过期了。

手动跑了一次 certbot renew,5分钟搞定。但因为是周六,客户发现的时候已经过了好几个小时,损失了一些订单。

这件事给我的教训是:证书管理不只是"申请+部署"。续期监控、多服务器同步、DNS变更的影响——这些环节没做好,早晚要出事。


证书的几种类型,快速过一下

DV证书(域名验证): 只验证域名所有权。Let’s Encrypt就是DV。申请最快,几分钟就下来,免费。绝大部分网站用这个就够了。

OV证书(组织验证): 验证域名+验证企业身份。申请要几天,要提交营业执照之类的材料,收费。

EV证书(扩展验证): 最严格。以前浏览器地址栏会显示绿色企业名,现在大部分浏览器已经取消这个UI了。所以EV和OV在用户感知上基本没区别。

说实话,对90%的网站来说,DV证书够了。OV和EV多出来的那点"企业可信度",大部分用户根本注意不到。

# 查看某网站用的什么证书
echo | openssl s_client -connect www.baidu.com:443 -servername www.baidu.com 2>/dev/null | openssl x509 -noout -issuer


单域名、通配符、多域名

单域名:只保护 example.com
通配符:*.example.com,覆盖所有一级子域名
多域名(SAN):一张证书保护多个不同域名

Let’s Encrypt支持单域名和通配符。通配符需要DNS验证——因为要证明你拥有整个域名的控制权,不能用HTTP验证。

# 单域名(HTTP验证,简单)
certbot –nginx -d example.com -d www.example.com

# 通配符(DNS验证)
certbot –manual –preferred-challenges dns -d example.com -d "*.example.com"

域名多了的话建议用通配符,省得一个子域名一张证书管不过来。

通配符证书配合DNS插件可以自动续期。如果你用Cloudflare或者阿里云DNS,certbot有对应插件,不用每次手动加DNS记录:

# 以Cloudflare为例
apt install python3-certbot-dns-cloudflare
certbot certonly –dns-cloudflare \\
–dns-cloudflare-credentials /path/to/credentials.ini \\
-d example.com -d "*.example.com"

这样通配符证书也能自动续期了。


部署:Nginx的完整SSL配置

server {
listen 443 ssl http2;
server_name example.com www.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:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 114.114.114.114 valid=300s;
resolver_timeout 5s;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

location / {
proxy_pass http://backend;
}
}

server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}

几个容易忽略的地方:

用fullchain.pem不是cert.pem。 fullchain.pem包含完整的证书链(你的证书+中间CA证书)。如果只用cert.pem,部分浏览器可能报"证书不受信任"——不是证书有问题,是中间证书没带上。

# 正确
ssl_certificate /path/to/fullchain.pem;

# 可能出问题
ssl_certificate /path/to/cert.pem;

OCSP Stapling要开。 不开的话,用户每次访问你的网站,浏览器都要去CA服务器验证证书是否有效,多了一次网络往返。开了OCSP Stapling,你的服务器提前拿到验证结果直接返回给浏览器,省掉了这次往返。高延迟场景下差别不小。

私钥文件权限。

chmod 600 /path/to/privkey.pem
chown root:root /path/to/privkey.pem

私钥泄露等于证书作废。谁拿到你的私钥谁就能冒充你的网站。

配置完验证一下:

nginx -t && nginx -s reload

# 在线全面检测
# ssllabs.com/ssltest 输入域名跑一次
# 目标:A+评级


续期:真正容易出事的环节

Let’s Encrypt证书90天过期。certbot默认每天检查两次是否需要续期(到期前30天内自动续期)。

# 检查定时任务是否存在
systemctl list-timers | grep certbot

如果没有,手动加crontab:

0 2,14 * * * certbot renew –quiet –deploy-hook "nginx -s reload"

注意那个 –deploy-hook。证书续期成功后要reload Nginx让新证书生效。很多人漏了这一步——证书续了,但Nginx还在用旧的,等于白续。

续期失败的常见原因

DNS变了。 就是开头那个客户的案例。换了DNS服务商、DNS记录没配好、DNS缓存没过期,都可能导致续期失败。

验证路径被拦了。 certbot用HTTP验证时会在 /.well-known/acme-challenge/ 下放一个文件,CA来检查。如果Nginx把这个路径重定向到了HTTPS(证书已经过期的HTTPS),或者被WAF拦了,验证就挂。

# 确保ACME验证路径能正常访问
location /.well-known/acme-challenge/ {
root /var/www/certbot;
allow all;
}

权限不够。 certbot没权限写证书目录。

配额限制。 Let’s Encrypt对每个域名每周有签发次数限制。频繁申请会触发限流。

必须加续期监控

不管自动续期配得多好,都要加一层人工监控。自动续期不是万无一失的。

#!/bin/bash
DOMAINS=("example.com" "api.example.com")
WARN_DAYS=14

for domain in "${DOMAINS[@]}"; do
expiry=$(echo | openssl s_client -connect "$domain:443" -servername "$domain" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now=$(date +%s)
days_left=$(( ($expiry_epoch – $now) / 86400 ))

if [ "$days_left" -le "$WARN_DAYS" ]; then
echo "[WARN] $domain 证书还有 ${days_left} 天过期"
# 这里接入你的告警渠道:企业微信/钉钉/邮件
fi
done

# 每天上午9点跑一次
0 9 * * * /root/check_cert.sh >> /var/log/cert_check.log 2>&1

告警阈值设14天。留两周处理续期问题。等到过期那天才告警,什么都来不及了。


多台服务器怎么管证书

方案一:每台各自申请续期

最简单,每台服务器各自装certbot各自管。

缺点是域名一多维护起来很烦。十几台服务器各有各的证书,续期状态要一台台检查。

方案二:一台申请,rsync同步

# 主服务器申请证书
certbot –nginx -d example.com

# deploy hook自动同步到其他服务器
cat > /etc/letsencrypt/renewal-hooks/deploy/sync.sh << 'EOF'
#!/bin/bash
SERVERS=("server2" "server3")
for s in "${SERVERS[@]}"; do
rsync -az /etc/letsencrypt/ "$s:/etc/letsencrypt/"
ssh "$s" "nginx -s reload"
done
EOF

chmod +x /etc/letsencrypt/renewal-hooks/deploy/sync.sh

证书集中在一台机器上管理,续期后自动同步。省事很多。

方案三:在负载均衡层做SSL终止(推荐)

用户 → 负载均衡器(证书在这里)→ 后端服务器(HTTP)

证书只需要在负载均衡器上部署一份。后端不操心证书的事。

# 负载均衡器
server {
listen 443 ssl;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;

location / {
proxy_pass http://backend_pool;
proxy_set_header X-Forwarded-Proto https;
}
}

前提:负载均衡器和后端在同一个安全网络里。如果中间链路不安全(比如跨机房),后端还是得用HTTPS。


CDN场景下的证书

用了CDN之后证书分两段:

用户 → CDN节点(CDN的证书)→ 源站(源站的证书)

CDN到用户: 大部分CDN自带免费证书,自动申请自动续期。你不用管。

CDN到源站: 如果CDN回源用HTTPS(推荐),源站需要证书。源站的证书可以用正式的也可以用自签的——CDN通常不校验源站证书的有效性。

# 如果CDN回源用的是HTTP(不推荐)
# CDN到源站这段是明文传输
# CDN和源站不在同一个机房的话,数据在中间链路上是裸的

建议回源用HTTPS。即使源站用自签证书,至少传输是加密的。


几个实战细节

自签证书什么场景用

内部服务之间通信、开发测试环境、CDN回源——不需要正式证书,自签的够用。

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \\
-keyout self.key -out self.crt \\
-subj "/CN=internal.example.com"

自签证书浏览器会报不信任。面向用户的场景必须用正式证书,没得商量。

证书链不完整

有些情况证书没过期但浏览器报"不信任"。大概率是证书链不完整——只部署了自己的证书,没带中间CA证书。

用Let’s Encrypt的 fullchain.pem 就不会出这个问题。别用 cert.pem。

域名多了怎么管

# 列出所有证书
certbot certificates

Certificate Name: example.com
Domains: example.com www.example.com
Expiry Date: 2025-11-08 (VALID: 67 days)

Certificate Name: api.example.com
Domains: api.example.com
Expiry Date: 2025-09-15 (VALID: 13 days) ← 快到期了

域名超过5个以上,建议用通配符+DNS插件自动续期。一个通配符覆盖所有子域名,只管一张证书。

Nginx配完reload不生效

有时候改了证书文件但Nginx reload之后还是用的旧证书。

# 检查Nginx实际加载的证书
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -serial -dates

如果还是旧证书,可能是Nginx没有真正reload(reload和restart不一样)。试试 nginx -s stop && nginx 或者 systemctl restart nginx。

还有一种情况:前面有CDN或者负载均衡器,你改的是源站的证书,但用户访问的是CDN的证书。这时候要确认CDN侧的证书状态。


检查清单

部署HTTPS的时候过一遍:

用的fullchain.pem(不是cert.pem)
TLS只允许1.2和1.3
开了OCSP Stapling
HTTP 301跳转到HTTPS
HSTS头加了
私钥权限600
自动续期配置了
deploy hook里有nginx reload
续期监控脚本跑了
多服务器的话证书同步方案确认了
CDN回源协议确认了(建议HTTPS)

最后跑一次 ssllabs.com/ssltest,目标A+。


总结

证书申请和部署本身不难,certbot几分钟就能搞定。真正容易出事的是后续管理。

续期失败没监控、多服务器证书不同步、换了DNS服务商续期就断了、私钥文件权限没锁紧——这些才是生产环境里证书出问题的常见原因。

花半天把证书管理搭好——自动续期、续期监控、多服务器同步。这半天能省掉以后无数次"证书过期全站挂掉"的半夜紧急处理。

别等出事了再补。


有问题评论区聊。

赞(0)
未经允许不得转载:171主机测评 » HTTPS证书这事儿,远不止申请完就完事了
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址