Node.js 通过 https 模块内置 HTTPS 支持。你加载证书文件并创建 HTTPS 服务器即可。本指南涵盖原生 Node.js、Express 以及推荐的生产环境配置。
基本 HTTPS 服务器
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('/etc/ssl/privkey.pem'),
cert: fs.readFileSync('/etc/ssl/fullchain.pem'),
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello HTTPS');
}).listen(443);
Express + HTTPS
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello HTTPS');
});
const options = {
key: fs.readFileSync('/etc/ssl/privkey.pem'),
cert: fs.readFileSync('/etc/ssl/fullchain.pem'),
};
https.createServer(options, app).listen(443, () => {
console.log('HTTPS server running on port 443');
});
将 HTTP 重定向到 HTTPS
同时运行 HTTP 和 HTTPS 服务器:
const http = require('http');
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
// ... your routes
const options = {
key: fs.readFileSync('/etc/ssl/privkey.pem'),
cert: fs.readFileSync('/etc/ssl/fullchain.pem'),
};
// HTTPS server
https.createServer(options, app).listen(443);
// HTTP redirect
http.createServer((req, res) => {
res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` });
res.end();
}).listen(80);
生产环境建议:反向代理
在生产环境中,不要直接在 Node.js 中终结 TLS。在前面使用反向代理(Nginx、Caddy 或负载均衡器):
Client ──HTTPS──→ Nginx (TLS termination) ──HTTP──→ Node.js (port 3000)
原因:
- Nginx 处理 TLS 更高效(C vs JavaScript)
- 证书续签不需要重启 Node.js
- 端口 443 需要 root 权限 — Node.js 不应以 root 运行
- 速率限制、缓存、压缩由代理处理
- HTTP/2 支持在 Nginx 中更成熟
Node.js 直接 HTTPS 适合开发、内部服务或小项目。
常见框架
Fastify
const fastify = require('fastify')({
https: {
key: fs.readFileSync('/etc/ssl/privkey.pem'),
cert: fs.readFileSync('/etc/ssl/fullchain.pem'),
},
});
fastify.get('/', async () => ({ hello: 'https' }));
fastify.listen({ port: 443, host: '0.0.0.0' });
Koa
const Koa = require('koa');
const https = require('https');
const fs = require('fs');
const app = new Koa();
app.use(ctx => { ctx.body = 'Hello HTTPS'; });
https.createServer({
key: fs.readFileSync('/etc/ssl/privkey.pem'),
cert: fs.readFileSync('/etc/ssl/fullchain.pem'),
}, app.callback()).listen(443);
Next.js / Nuxt / 其他框架开发服务器
框架开发服务器通常有 --https 参数用于本地开发。生产环境中,始终使用反向代理——这些框架在 Nginx、Caddy 或云负载均衡器后面提供静态文件或运行 SSR。
环境变量模式
不要硬编码证书路径。使用环境变量让同一份代码在开发和生产环境中都能工作:
const options = process.env.SSL_KEY ? {
key: fs.readFileSync(process.env.SSL_KEY),
cert: fs.readFileSync(process.env.SSL_CERT),
} : null;
if (options) {
https.createServer(options, app).listen(443);
console.log('HTTPS server on port 443');
} else {
app.listen(3000);
console.log('HTTP server on port 3000 (no SSL_KEY set)');
}
使用自签名证书进行开发
本地开发时生成自签名证书(不用于生产):
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
-keyout dev-key.pem -out dev-cert.pem -days 365 -nodes \
-subj "/CN=localhost"
const options = {
key: fs.readFileSync('./dev-key.pem'),
cert: fs.readFileSync('./dev-cert.pem'),
};
浏览器会显示警告(自签名证书不受信任)——开发时点击通过即可。
热重载证书
无需重启即可重载证书(对 Let’s Encrypt 续签有用):
const tls = require('tls');
function loadCerts() {
return {
key: fs.readFileSync('/etc/ssl/privkey.pem'),
cert: fs.readFileSync('/etc/ssl/fullchain.pem'),
};
}
const server = https.createServer({
SNICallback: (hostname, cb) => {
const ctx = tls.createSecureContext(loadCerts());
cb(null, ctx);
},
}, app);
这会在每个新连接时重新读取文件。为了更好的性能,添加一个文件监听器,仅在文件变化时重载。
常见问题
应该在 Node.js 中处理 SSL 还是用反向代理?
生产环境:用反向代理。开发、小项目或内部服务:Node.js 直接 HTTPS 没问题。反向代理模式是标准做法,因为它分离了关注点并更高效地处理 TLS。
可以在 Node.js 中使用 GetHTTPS 证书吗?
可以。GetHTTPS 生成标准 PEM 文件(privkey.pem、fullchain.pem),Node.js 通过 fs.readFileSync() 直接读取。
如何在 Node.js 中处理证书续签?
如果使用反向代理,只需替换文件并重新加载代理——Node.js 不需要重启。如果直接处理 TLS,使用上面的 SNICallback 方法或在替换证书文件后重启 Node.js 进程。
localhost 开发的 SSL 怎么处理?
使用自签名证书(如上所示)或 mkcert 获取本地信任的开发证书。不要为 localhost 使用 Let’s Encrypt——它无法验证非公开可访问的域名。
可以在 Deno 或 Bun 中使用 GetHTTPS 证书吗?
可以。Deno 和 Bun 都支持 PEM 文件的 TLS:
Deno:
Deno.serve({
port: 443,
cert: Deno.readTextFileSync("/etc/ssl/fullchain.pem"),
key: Deno.readTextFileSync("/etc/ssl/privkey.pem"),
}, (req) => new Response("Hello HTTPS"));
Bun:
Bun.serve({
port: 443,
tls: {
cert: Bun.file("/etc/ssl/fullchain.pem"),
key: Bun.file("/etc/ssl/privkey.pem"),
},
fetch(req) { return new Response("Hello HTTPS"); },
});
来自 GetHTTPS 的同一份 PEM 文件适用于所有 JavaScript 运行时。