Node.js tiene soporte HTTPS integrado a través del módulo https. Cargas tus archivos de certificado y creas un servidor HTTPS. Esta guía cubre Node.js puro, Express y la configuración recomendada para producción.
Servidor HTTPS básico
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 con 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');
});
Redirigir HTTP a HTTPS
Ejecuta tanto un servidor HTTP como 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);
Recomendación para producción: proxy inverso
Para producción, no termines TLS directamente en Node.js. Usa un proxy inverso (Nginx, Caddy o un balanceador de carga) delante:
Client ──HTTPS──→ Nginx (TLS termination) ──HTTP──→ Node.js (port 3000)
Razones:
- Nginx maneja TLS más eficientemente (C vs JavaScript)
- La renovación de certificados no requiere reiniciar Node.js
- El puerto 443 requiere root — Node.js no debería ejecutarse como root
- Limitación de tasa, caché, compresión manejados por el proxy
- HTTP/2 tiene soporte más maduro en Nginx
Node.js con HTTPS directo está bien para desarrollo, servicios internos o proyectos pequeños.
Frameworks comunes
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 / otros servidores de desarrollo de frameworks
Los servidores de desarrollo de frameworks generalmente tienen un flag --https para desarrollo local. Para producción, siempre usa un proxy inverso: estos frameworks sirven archivos estáticos o ejecutan SSR detrás de Nginx, Caddy o un balanceador de carga en la nube.
Patrón con variables de entorno
No codifiques las rutas de los certificados directamente. Usa variables de entorno para que el mismo código funcione en desarrollo y producción:
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)');
}
Desarrollo con certificados autofirmados
Para desarrollo local, genera un certificado autofirmado (no para producción):
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'),
};
Tu navegador mostrará una advertencia (los certificados autofirmados no son de confianza); haz clic para continuar durante el desarrollo.
Recarga de certificados en caliente
Para recargar certificados sin reiniciar (útil para la renovación de 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);
Esto vuelve a leer los archivos en cada nueva conexión. Para mejor rendimiento, añade un observador de archivos que recargue solo cuando los archivos cambien.
Preguntas frecuentes
¿Debo manejar SSL en Node.js o usar un proxy inverso?
Para producción: usa un proxy inverso. Para desarrollo, proyectos pequeños o servicios internos: Node.js con HTTPS directo está bien. El patrón de proxy inverso es estándar porque separa responsabilidades y maneja TLS más eficientemente.
¿Puedo usar certificados de GetHTTPS con Node.js?
Sí. GetHTTPS produce archivos PEM estándar (privkey.pem, fullchain.pem) que Node.js lee directamente con fs.readFileSync().
¿Cómo manejo la renovación de certificados en Node.js?
Si usas un proxy inverso, simplemente reemplaza los archivos y recarga el proxy; Node.js no necesita reiniciarse. Si manejas TLS directamente, usa el enfoque de SNICallback mostrado arriba o reinicia el proceso de Node.js después de reemplazar los archivos del certificado.
¿Qué hay de SSL en localhost para desarrollo?
Usa un certificado autofirmado (mostrado arriba) o mkcert para un certificado de desarrollo localmente confiable. No uses Let’s Encrypt para localhost: no puede validar un dominio que no es accesible públicamente.
¿Puedo usar certificados de GetHTTPS con Deno o Bun?
Sí. Tanto Deno como Bun soportan TLS con archivos PEM:
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"); },
});
Los mismos archivos PEM de GetHTTPS funcionan en todos los runtimes de JavaScript.