概要
某云服务器上的加密代理服务,在运行稳定数月后,于某日突然出现所有客户端无法连接的情况。网站访问正常,但加密代理完全失效。本文记录了从初步检测到最终定位问题的全过程。
一、问题现象
在问题出现前,所有客户端均能正常连接。某日夜间起,连接全部失败,日志中出现大量类似以下信息:
TLS handshake error: remote error: tls: bad certificate
此错误表明 TLS 握手阶段证书验证失败,但当时并不清楚是客户端、服务端、还是中间环节的问题。
二、初步判断:排除外部因素
首先确认网络层和基础设施:
- 云主机位于日本节点;
- 日本本地与境外客户端均无法连接;
- 网站 HTTPS 服务(由同机 Nginx 提供)一切正常;
- 因此可排除防火墙或区域性网络封锁问题。
故障范围仅限于代理服务端口,指向服务自身配置问题。
三、证书与握手检测
通过 openssl s_client 对两个服务端口进行 TLS 测试。
网站端口(正常服务):
Verify return code: 0 (ok)
issuer: CN=R13
notAfter: Jan 10 2026 GMT
代理端口(故障服务):
Verify return code: 10 (certificate has expired)
issuer: CN=R11
对比结果表明:
- 网站端口使用的是新证书(R13),仍在有效期内;
- 代理端口使用的是旧证书(R11),已过期。
即同一主机上存在两份不同的证书链,代理服务仍在使用旧版。
四、定位原因
进一步检查配置文件,代理服务的 TLS 设置如下:
"tlsSettings": {
"certificates": [{
"certificateFile": "/etc/letsencrypt/live/xxx/fullchain.pem",
"keyFile": "/etc/letsencrypt/live/xxx/privkey.pem"
}]
}
路径正确,指向 live 目录的符号链接;但查看系统日志后发现,该进程已连续运行超过一周,而新证书的签发时间正是数天前。
说明该进程在新证书生成后从未重启过。
由于此类服务不会自动重新加载证书文件,进程一直在使用内存中的旧证书。直到旧证书过期后,客户端才全部无法建立 TLS 握手。
五、解决与验证
重启服务后,再次检测:
Verify return code: 0 (ok)
issuer: CN=R13
notAfter: Jan 10 2026 GMT
握手恢复正常,客户端均可重新连接。
为防止同类问题再次发生,添加了自动重载机制。
六、预防措施
1. 在 Certbot 续期后自动重启相关服务
在 /etc/letsencrypt/renewal-hooks/deploy/ 下创建脚本:
#!/bin/bash
if echo "$RENEWED_LINEAGE" | grep -q "/live/xxx"; then
systemctl reload nginx 2>/dev/null || true
systemctl restart proxy-service || true
fi
每次证书续期成功后将自动重启代理服务。
2. 或使用 systemd.path 监控证书文件变化
[Path]
PathChanged=/etc/letsencrypt/live/xxx/fullchain.pem
当证书文件更新时自动触发 systemctl restart proxy-service。
七、结论
本次事件的根因是 服务长期未重启导致旧证书仍在内存中生效。
该问题通常表现为:
- 网站 HTTPS 正常;
- 代理或其他直连 TLS 服务全部失败;
- 日志中持续出现
tls: bad certificate。
在启用 Let’s Encrypt 自动续期时,所有使用证书的进程必须支持热加载或在续期后自动重启,否则将在证书过期时出现中断。
经验总结
- 使用
openssl s_client是定位 TLS 问题最直接的方法; - 同一主机不同服务若使用同一证书,应保持统一的 reload 机制;
- 续期成功 ≠ 生效成功,重启或热加载同样关键;
- 自动化脚本是防止“昨天还好、今天全挂”的根本方案。
关键词:Let’s Encrypt、TLS、证书过期、v2ray、自动续期、系统维护