朕喜技术(ZhenxiJS)
2026.09.01
1 分钟阅读

Node.js HTTPS 服务 TLS 证书过期修复实战:SSL certificate expired 排查与解决

日期:2026-09-01 | 技术栈:Node.js / OpenSSL / TLS / HTTPS / Docker Compose 关键词:SSL certificate expired、certificate has expired、TLS 证书过期、openssl s_client、Node.js HTTPS、自签名证书、curl cacert

一、问题背景

维护的 Node.js HTTPS 服务突然无法访问,浏览器报「您的连接不安全 / 证书已过期」。但 netstat 显示 8443 端口正常监听,node 进程也在运行——服务进程没挂,只是 TLS 证书过期导致所有 HTTPS 客户端握手失败。

二、复现步骤

# 1. 启动服务环境
cd scenario/2026-09-01-0800
docker compose up -d --build

# 2. 检查服务器证书有效期(跳过信任校验)
echo | openssl s_client -connect localhost:8443 2>/dev/null | openssl x509 -noout -dates
# 输出:notBefore=Jan  1 00:00:00 2020 GMT
#       notAfter=Jan  2 00:00:00 2020 GMT

# 3. 用 CA 证书完成信任校验 → 暴露证书过期
curl -v --cacert certs/ca.crt https://localhost:8443
# 报错:curl: (60) SSL certificate problem: certificate has expired

三、根本原因

服务器使用了一个有效期仅 1 天的已过期证书(2020-01-01 到 2020-01-02)。当客户端(curl/浏览器)建立 TLS 握手时,会校验服务器证书的有效期,发现 notAfter 时间已过去,直接拒绝连接。

服务进程本身没有崩溃,但 TLS 层无法完成握手,所以所有 HTTPS 请求都会失败。

为什么 curl 报两种不同的错误?

  • 直接 curl 访问:报 unable to get local issuer certificate — 因为自签名 CA 不被系统信任。
  • --cacert 指定 CA 后:报 certificate has expired — 信任链能走了,但发现证书已过期。

四、解决办法

4.1 生成新的有效证书(365 天有效期)

# 创建 OpenSSL 配置,设置 default_days = 365
# 生成新的 CA 和服务器证书
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout ca.key -out ca.crt -days 3650 -subj "/CN=TestCA"

openssl req -new -newkey rsa:2048 -nodes \
  -keyout server.key -out server.csr -subj "/CN=localhost"

openssl ca -config openssl.cnf -in server.csr \
  -out server.crt -notext -batch

4.2 替换证书文件并重启服务

cp server.key server.crt ca.crt /workspace/certs/
docker compose restart

4.3 验证修复

echo | openssl s_client -connect localhost:8443 2>/dev/null | openssl x509 -noout -dates
# notBefore=Sep  1 00:07:49 2026 GMT
# notAfter=Sep  1 00:07:49 2027 GMT

curl -s --cacert certs/ca.crt https://localhost:8443
# Hello! This is an HTTPS service. If you can see this, the TLS handshake succeeded!

五、验证

  • ✅ openssl s_client 显示新证书 notBefore=2026-09-01,notAfter=2027-09-01(有效期 365 天)
  • ✅ curl --cacert certs/ca.crt https://localhost:8443 返回 200 OK
  • ✅ TLS 握手日志显示 SSL certificate verify ok.

六、经验与避坑

  1. 服务进程没挂 ≠ 服务可用:HTTPS 服务即使进程正常运行,证书过期后所有客户端都无法连接。监控时要同时关注证书有效期。
  2. Docker 容器中 node 是 PID 1:不能直接 kill -9 node 进程,否则容器会退出。应使用 docker compose restart。
  3. Node.js 不热加载证书:替换证书文件后必须重启进程,否则旧证书仍在内存中使用。
  4. openssl s_client 是排查 TLS 问题的利器:echo | openssl s_client -connect host:port 2>/dev/null | openssl x509 -noout -dates 可快速查看服务器证书有效期。
  5. 自签名证书需要 --cacert 指定 CA:否则 curl 先报 unable to get local issuer certificate,掩盖了真实的证书过期问题。
  6. 生产环境应使用证书自动续期:如 Let's Encrypt + certbot,或用 ACME 协议自动管理证书,避免手动操作遗漏导致证书过期事故。

可复现命令

git checkout 2026-09-01-0800
cd scenario/2026-09-01-0800
docker compose up -d --build
echo | openssl s_client -connect localhost:8443 2>/dev/null | openssl x509 -noout -dates
curl -v --cacert certs/ca.crt https://localhost:8443

版权声明

本文为博主原创文章,遵循 CC BY-NC-SA 4.0 许可协议。 转载请注明出处:http://localhost/blogs/2026-09-01-0808-tls

分享这篇文章