You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js Express启用SNICallback后证书验证失败问题求助

问题描述

我用Let's Encrypt证书搭建了Node.js Express HTTPS服务,原配置可正常被浏览器及其他应用访问。为实现证书自动续期无需重启服务,我通过SNICallback结合tls安全上下文实时读取证书链。修改后浏览器可正常获取新证书,但其他应用连接时出现错误:Error: unable to verify the first certificate,疑似服务器未提供中间证书。

原配置代码

var express = require('express');
var fs = require('fs');
var http = require('http');
let https = require('https');

const app = express();

let privateKey  = fs.readFileSync(SERVER_KEY, 'utf8');
let certificate = fs.readFileSync(SERVER_CRT, 'utf8');
let certauth = fs.readFileSync(SERVER_CA, 'utf8');
let credentials = {key: privateKey, cert: certificate, ca: certauth};

//HTTPS server
const httpsServer = https.createServer(credentials, app);
httpsServer.listen(443);

//HTTP redirect server
const httpServer = http.createServer(function (req, res) {
    res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
    res.end();
});
httpServer.listen(80);

证书链参数

SERVER_KEY=/etc/letsencrypt/live/dummy.example.com/privkey.pem
SERVER_CRT=/etc/letsencrypt/live/dummy.example.com/cert.pem
SERVER_CA=/etc/letsencrypt/live/dummy.example.com/chain.pem

修改后的配置代码

var express = require('express');
var fs = require('fs');
var http = require('http');
let https = require('https');
var tls = require('tls');       

const app = express();

function getCredentials(){
    let privateKey  = fs.readFileSync(SERVER_KEY, 'utf8');
    let certificate = fs.readFileSync(SERVER_CRT, 'utf8');
    let certauth = fs.readFileSync(SERVER_CA, 'utf8');
    let credentials = {key: privateKey, cert: certificate, ca: certauth};
    return credentials;
}
var ctx = function() { return tls.createSecureContext(getCredentials()) };

//HTTPS server
const httpsServer = https.createServer({
    SNICallback: (servername, cb) => cb(null, ctx())
}, app);
httpsServer.listen(443);

//HTTP redirect server
const httpServer = http.createServer(function (req, res) {
    res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
    res.end();
});
httpServer.listen(80);
解决方案

问题根源在于SNICallback返回的安全上下文里,cert字段仅传入了服务器证书,未包含中间证书链。浏览器会自动尝试补充缺失的中间证书,但多数非浏览器应用不会做这件事,必须手动将服务器证书与中间证书拼接后作为cert的值。

修改getCredentials函数,合并服务器证书和中间证书链:

function getCredentials(){
    let privateKey  = fs.readFileSync(SERVER_KEY, 'utf8');
    // 合并服务器证书与中间证书链
    let certificate = fs.readFileSync(SERVER_CRT, 'utf8') + fs.readFileSync(SERVER_CA, 'utf8');
    let credentials = {key: privateKey, cert: certificate};
    return credentials;
}

另外说明:tls.createSecureContext中的ca字段是用来验证客户端证书的,并非服务器返回给客户端的证书链。原配置里的ca参数属于冗余设置,因为Let's Encrypt的根证书已在多数系统的信任存储中。

修改完成后,SNICallback返回的安全上下文会同时发送服务器证书和中间证书,非浏览器应用即可正常验证证书链。

内容的提问来源于stack exchange,提问作者nick_j_white

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 19:13:10