Last updated: 2026-06-25
Node.js TLS/SSL Configuration Guide
This guide provides recommended TLS/SSL settings for Node.js applications. Node.js uses the built-in tls and https modules, which wrap OpenSSL, to provide encrypted connections for both servers and clients.
Prerequisites
- Node.js 24 or later (Active LTS); Node.js 22 is Maintenance LTS (until April 2027); Node.js 18 reached end of life April 30, 2025
- OpenSSL 1.1.1 or later (bundled with Node.js)
- A valid TLS certificate and private key (PEM format)
HTTPS Server Configuration
Basic HTTPS Server
Use https.createServer() with TLS options to serve HTTPS:
const https = require('node:https');
const fs = require('node:fs');
const options = {
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
key: fs.readFileSync('/etc/ssl/private/server.key'),
ca: fs.readFileSync('/etc/ssl/certs/ca.crt'),
};
const server = https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello, TLS!\n');
});
server.listen(443);
TLS Protocol Versions
Restrict connections to TLS 1.2 and TLS 1.3:
const options = {
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
key: fs.readFileSync('/etc/ssl/private/server.key'),
minVersion: 'TLSv1.2',
maxVersion: 'TLSv1.3',
};
Cipher Suite Configuration
Specify strong cipher suites for TLS 1.2. TLS 1.3 suite selection is done by including the TLS_AES_* / TLS_CHACHA20_* names in the ciphers list; the default three TLS 1.3 suites are already secure.
const options = {
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
key: fs.readFileSync('/etc/ssl/private/server.key'),
minVersion: 'TLSv1.2',
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-CHACHA20-POLY1305',
'ECDHE-RSA-CHACHA20-POLY1305',
].join(':'),
honorCipherOrder: true,
};
ECDH Curve
Specify the ECDH curves for key exchange:
const options = {
// ...certificates...
ecdhCurve: 'X25519:P-256:P-384',
};
Secure Options
Disable insecure features via secureOptions:
const crypto = require('node:crypto');
const options = {
// ...certificates...
secureOptions:
crypto.constants.SSL_OP_NO_SSLv2 |
crypto.constants.SSL_OP_NO_SSLv3 |
crypto.constants.SSL_OP_NO_TLSv1 |
crypto.constants.SSL_OP_NO_TLSv1_1 |
crypto.constants.SSL_OP_NO_COMPRESSION,
};
When using
minVersion: 'TLSv1.2', theSSL_OP_NO_*flags for older protocols are set automatically. TheSSL_OP_NO_COMPRESSIONflag is still useful to explicitly disable TLS compression.
Client Certificate Verification (mTLS)
Require and verify client certificates for mutual TLS:
const options = {
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
key: fs.readFileSync('/etc/ssl/private/server.key'),
ca: fs.readFileSync('/etc/ssl/certs/ca.crt'),
requestCert: true,
rejectUnauthorized: true,
};
Access client certificate details in the request handler:
const server = https.createServer(options, (req, res) => {
const cert = req.socket.getPeerCertificate();
console.log('Client CN:', cert.subject.CN);
res.writeHead(200);
res.end('Authenticated\n');
});
See RFC 8446 ยง4.3.2 for the TLS Certificate Request specification, and Wikipedia: Mutual authentication for a general overview.
Express.js HTTPS
Wrap an Express application with HTTPS:
const https = require('node:https');
const fs = require('node:fs');
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, TLS!');
});
const options = {
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
key: fs.readFileSync('/etc/ssl/private/server.key'),
minVersion: 'TLSv1.2',
ciphers: [
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-CHACHA20-POLY1305',
'ECDHE-RSA-CHACHA20-POLY1305',
].join(':'),
honorCipherOrder: true,
};
https.createServer(options, app).listen(443);
TLS for Outbound Connections
HTTPS Client Requests
Configure TLS when making outbound HTTPS requests:
const https = require('node:https');
const options = {
hostname: 'api.example.com',
port: 443,
path: '/data',
method: 'GET',
ca: fs.readFileSync('/etc/ssl/certs/ca.crt'),
minVersion: 'TLSv1.2',
// For client certificate authentication:
cert: fs.readFileSync('/etc/ssl/certs/client.crt'),
key: fs.readFileSync('/etc/ssl/private/client.key'),
};
const req = https.request(options, (res) => {
// handle response
});
req.end();
Low-Level TLS Connections
Use tls.connect() for non-HTTP TLS connections:
const tls = require('node:tls');
const options = {
host: 'db.example.com',
port: 5432,
ca: fs.readFileSync('/etc/ssl/certs/ca.crt'),
minVersion: 'TLSv1.2',
checkServerIdentity: tls.checkServerIdentity,
};
const socket = tls.connect(options, () => {
console.log('Connected, authorized:', socket.authorized);
});
Complete Server Example
const https = require('node:https');
const crypto = require('node:crypto');
const fs = require('node:fs');
const server = https.createServer(
{
// Certificates
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
key: fs.readFileSync('/etc/ssl/private/server.key'),
ca: fs.readFileSync('/etc/ssl/certs/ca.crt'),
// Protocol versions
minVersion: 'TLSv1.2',
maxVersion: 'TLSv1.3',
// Cipher suites (TLS 1.2)
ciphers: [
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-CHACHA20-POLY1305',
'ECDHE-RSA-CHACHA20-POLY1305',
].join(':'),
honorCipherOrder: true,
// ECDH curves
ecdhCurve: 'X25519:P-256:P-384',
// Security options
secureOptions: crypto.constants.SSL_OP_NO_COMPRESSION,
// Session tickets
sessionTimeout: 300,
},
(req, res) => {
res.writeHead(200, { 'Strict-Transport-Security': 'max-age=63072000' });
res.end('Secure response\n');
}
);
server.listen(443, () => {
console.log('HTTPS server listening on port 443');
});
Security Notes
Node.js uses OpenSSL (via libuv) for TLS. The specific version of OpenSSL bundled with your Node.js release governs the available fixes:
- POODLE (CVE-2014-3566, 2014): SSL 3.0 is disabled. TLS_FALLBACK_SCSV was added in OpenSSL 1.0.1j / 1.0.2 (October 2014); SSL 3.0 disabled by default in OpenSSL 1.1.0 (August 2016).
- BEAST (CVE-2011-3389, 2011): Mitigated by recommending TLS 1.2 as the minimum; AEAD-only ciphers eliminate the CBC padding oracle.
- CRIME (CVE-2012-4929, 2012): TLS compression is off by default in OpenSSL 1.1.0+; do not enable it.
- Lucky13 (2013): AEAD-only cipher list eliminates CBC padding timing side-channels entirely.
- FREAK (CVE-2015-0204, 2015): EXPORT-grade ciphers are excluded from the cipher string. Removed from OpenSSL 1.1.0 (August 2016).
- LOGJAM (CVE-2015-4000, 2015): Short-key DHE is excluded; only ECDHE key exchange is recommended.
- Sweet32 (CVE-2016-2183, 2016): 3DES is excluded from the cipher string. Node.js 22+ ships with OpenSSL 3.x.
- ROBOT (2017): Static RSA key exchange is excluded; only ECDHE is recommended.
- Downgrade attacks: TLS_FALLBACK_SCSV prevents protocol version rollback.
- Renegotiation injection (CVE-2009-3555, 2009): Secure renegotiation is enforced by default in OpenSSL 0.9.8m+; TLS 1.3 removes renegotiation entirely.
The following are not addressable through TLS configuration alone:
- Heartbleed (CVE-2014-0160, 2014): Affected Node.js versions using OpenSSL 1.0.1 through 1.0.1f. Fixed in OpenSSL 1.0.1g (April 7, 2014). All current Node.js LTS releases use OpenSSL versions well past this fix. Addressed by keeping Node.js up to date.
- BREACH (CVE-2013-3587, 2013): Exploits HTTP-level response compression (gzip/deflate on responses). Mitigated at the application layer by disabling HTTP compression or using BREACH countermeasures; TLS configuration cannot prevent it.
- DROWN (CVE-2016-0800, 2016): Requires SSLv2 to be enabled on any server sharing the same private key. Ensure SSLv2 is disabled on all services that use the same certificate and key pair.
Verification
Test your server with openssl s_client:
openssl s_client -connect localhost:443 -tls1_2
openssl s_client -connect localhost:443 -tls1_3
Check the negotiated protocol and cipher:
echo | openssl s_client -connect localhost:443 2>/dev/null | grep -E 'Protocol|Cipher'
Verify from Node.js itself:
const tls = require('node:tls');
const socket = tls.connect({ host: 'localhost', port: 443, rejectUnauthorized: false }, () => {
console.log('Protocol:', socket.getProtocol());
console.log('Cipher:', socket.getCipher());
socket.end();
});