Overview
The node:tls module provides an implementation of the Transport Layer Security (TLS) and Secure Socket Layer (SSL) protocols built on top of OpenSSL.
const tls = require('node:tls');
const fs = require('node:fs');
const options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
};
const server = tls.createServer(options, (socket) => {
console.log('server connected',
socket.authorized ? 'authorized' : 'unauthorized');
socket.write('welcome!\n');
socket.setEncoding('utf8');
socket.pipe(socket);
});
server.listen(8000, () => {
console.log('server bound');
});
Core Concepts
TLS/SSL Overview
TLS/SSL protocols rely on a public key infrastructure (PKI) to enable secure communication between client and server. Key components:
- Private Keys - Secret keys used for decryption and signing
- Certificates - Public keys signed by a Certificate Authority
- Certificate Authorities (CA) - Trusted entities that sign certificates
Perfect Forward Secrecy
PFS ensures that session keys are not compromised even if the server’s private key is compromised:
- ECDHE (Elliptic Curve Diffie-Hellman Ephemeral) - Enabled by default
- DHE (Diffie-Hellman Ephemeral) - Can be enabled with
dhparam: 'auto'
TLSv1.3 always uses (EC)DHE for perfect forward secrecy.
ALPN and SNI
ALPN (Application-Layer Protocol Negotiation)
- Allows one TLS server to support multiple protocols (HTTP, HTTP/2)
SNI (Server Name Indication)
- Allows one TLS server to serve multiple hostnames with different certificates
Pre-shared Keys (PSK)
TLS-PSK provides alternative authentication using pre-shared keys instead of certificates:
const server = tls.createServer({
ciphers: 'PSK+ECDHE',
pskCallback(socket, identity) {
if (identity === 'client1') {
return Buffer.from('shared-secret');
}
return null;
}
});
PSK should only be used when secure key distribution is possible. Deriving PSK from passwords is not secure.
Creating TLS Servers
tls.createServer([options][, secureConnectionListener])
Create a TLS server:
const tls = require('node:tls');
const fs = require('node:fs');
const options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
ca: [fs.readFileSync('ca-cert.pem')],
requestCert: true,
rejectUnauthorized: false
};
const server = tls.createServer(options, (socket) => {
console.log('Client connected:', socket.authorized);
if (socket.authorized) {
socket.write('Authorized connection\n');
} else {
socket.write('Unauthorized: ' + socket.authorizationError + '\n');
}
socket.on('data', (data) => {
console.log('Received:', data.toString());
});
});
server.listen(8000);
Common Options:
key - Private key in PEM format
cert - Certificate in PEM format
ca - Array of trusted CA certificates
ciphers - Cipher suite specification
requestCert - Request client certificate
rejectUnauthorized - Reject unauthorized clients
minVersion - Minimum TLS version (e.g., ‘TLSv1.2’)
maxVersion - Maximum TLS version (e.g., ‘TLSv1.3’)
dhparam - Diffie-Hellman parameters (‘auto’ recommended)
ecdhCurve - ECDH curves to use
sessionTimeout - Session timeout in seconds
ticketKeys - 48-byte buffer for session ticket keys
Creating TLS Clients
tls.connect(options[, callback])
Connect to a TLS server:
const tls = require('node:tls');
const fs = require('node:fs');
const options = {
host: 'example.com',
port: 443,
ca: [fs.readFileSync('ca-cert.pem')],
key: fs.readFileSync('client-key.pem'),
cert: fs.readFileSync('client-cert.pem')
};
const socket = tls.connect(options, () => {
console.log('Connected:',
socket.authorized ? 'authorized' : 'unauthorized');
socket.write('GET / HTTP/1.0\r\n\r\n');
});
socket.on('data', (data) => {
console.log(data.toString());
});
socket.on('end', () => {
console.log('Connection ended');
});
Connection Options:
host - Server hostname
port - Server port
servername - Server name for SNI
checkServerIdentity - Custom server identity check function
session - Reuse TLS session
minVersion, maxVersion - TLS version constraints
ca, key, cert - Client certificates
rejectUnauthorized - Reject invalid certificates (default: true)
Class: tls.Server
Extends net.Server to provide TLS/SSL server functionality.
Events
’secureConnection’
Emitted when handshake completes:
server.on('secureConnection', (tlsSocket) => {
console.log('Secure connection established');
console.log('Protocol:', tlsSocket.getProtocol());
console.log('Cipher:', tlsSocket.getCipher());
console.log('ALPN Protocol:', tlsSocket.alpnProtocol);
});
‘newSession’
Emitted when new TLS session is created:
const sessions = {};
server.on('newSession', (sessionId, sessionData, callback) => {
sessions[sessionId.toString('hex')] = sessionData;
callback();
});
‘resumeSession’
Emitted when client requests to resume session:
server.on('resumeSession', (sessionId, callback) => {
const data = sessions[sessionId.toString('hex')];
callback(null, data || null);
});
‘tlsClientError’
Emitted when error occurs before secure connection:
server.on('tlsClientError', (err, tlsSocket) => {
console.error('TLS Error:', err.message);
});
‘keylog’
Emitted when key material is generated:
const logFile = fs.createWriteStream('ssl-keys.log', { flags: 'a' });
server.on('keylog', (line, tlsSocket) => {
logFile.write(line);
});
Methods
server.addContext(hostname, context)
Add SNI context for specific hostname:
server.addContext('example.com', {
key: fs.readFileSync('example-key.pem'),
cert: fs.readFileSync('example-cert.pem')
});
server.addContext('*.wildcard.com', {
key: fs.readFileSync('wildcard-key.pem'),
cert: fs.readFileSync('wildcard-cert.pem')
});
server.getTicketKeys()
Get 48-byte session ticket keys:
const keys = server.getTicketKeys();
server.setTicketKeys(keys)
Set session ticket keys:
const newKeys = crypto.randomBytes(48);
server.setTicketKeys(newKeys);
Session ticket keys are cryptographic secrets and must be stored securely. Rotate them regularly.
Class: tls.TLSSocket
Extends net.Socket with TLS/SSL encryption.
Properties
tlsSocket.authorized
Boolean indicating if peer certificate was signed by trusted CA:
if (socket.authorized) {
console.log('Certificate is valid');
} else {
console.log('Certificate error:', socket.authorizationError);
}
tlsSocket.authorizationError
Reason why certificate was not authorized
tlsSocket.encrypted
Always true for TLS sockets
tlsSocket.alpnProtocol
Selected ALPN protocol:
console.log('ALPN:', socket.alpnProtocol || 'not negotiated');
tlsSocket.servername
Server name from SNI
Methods
tlsSocket.getCipher()
Get cipher suite information:
const cipher = socket.getCipher();
console.log('Name:', cipher.name);
console.log('Version:', cipher.version);
tlsSocket.getProtocol()
Get TLS protocol version:
console.log('Protocol:', socket.getProtocol());
// 'TLSv1.3'
tlsSocket.getPeerCertificate([detailed])
Get peer’s certificate:
const cert = socket.getPeerCertificate();
console.log('Subject:', cert.subject);
console.log('Issuer:', cert.issuer);
console.log('Valid from:', cert.valid_from);
console.log('Valid to:', cert.valid_to);
tlsSocket.getSession()
Get TLS session for resumption:
const session = socket.getSession();
// Reuse in new connection:
const newSocket = tls.connect({
session: session,
// ... other options
});
tlsSocket.getTLSTicket()
Get session ticket:
const ticket = socket.getTLSTicket();
if (ticket) {
console.log('Session ticket available');
}
tlsSocket.renegotiate(options, callback)
Initiate TLS renegotiation:
socket.renegotiate({
rejectUnauthorized: true,
requestCert: true
}, (err) => {
if (err) console.error('Renegotiation failed:', err);
});
TLSv1.3 does not support renegotiation. Renegotiation is limited to 3 times per 10 minutes to prevent DoS attacks.
Secure Context
tls.createSecureContext([options])
Create reusable secure context:
const context = tls.createSecureContext({
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
ca: [fs.readFileSync('ca.pem')],
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256',
honorCipherOrder: true,
minVersion: 'TLSv1.2'
});
const server = tls.createServer({ secureContext: context });
Cipher Suites
Default Cipher Suite
View default ciphers:
node -p crypto.constants.defaultCoreCipherList
Default includes:
- TLS_AES_256_GCM_SHA384 (TLSv1.3)
- TLS_CHACHA20_POLY1305_SHA256 (TLSv1.3)
- TLS_AES_128_GCM_SHA256 (TLSv1.3)
- ECDHE-RSA-AES128-GCM-SHA256
- ECDHE-ECDSA-AES128-GCM-SHA256
Customizing Ciphers
const server = tls.createServer({
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'!aNULL',
'!eNULL',
'!EXPORT',
'!DES',
'!RC4',
'!MD5'
].join(':'),
honorCipherOrder: true
});
Security Levels
Set OpenSSL security level:
const server = tls.createServer({
ciphers: 'DEFAULT@SECLEVEL=2',
minVersion: 'TLSv1.2'
});
Security levels:
- Level 0 - Everything permitted
- Level 1 - 80-bit security minimum
- Level 2 - 112-bit security minimum (default)
- Level 3 - 128-bit security minimum
- Level 4 - 192-bit security minimum
- Level 5 - 256-bit security minimum
Session Resumption
Session Identifiers
Server stores session state:
const sessions = new Map();
server.on('newSession', (id, data, cb) => {
sessions.set(id.toString('hex'), data);
cb();
});
server.on('resumeSession', (id, cb) => {
const data = sessions.get(id.toString('hex'));
cb(null, data);
});
Session Tickets
Server encrypts session state and sends to client:
const server = tls.createServer({
ticketKeys: crypto.randomBytes(48),
sessionTimeout: 300 // 5 minutes
});
// Rotate keys periodically
setInterval(() => {
server.setTicketKeys(crypto.randomBytes(48));
}, 3600000); // Every hour
Certificate Verification
Custom Verification
const socket = tls.connect({
host: 'example.com',
port: 443,
checkServerIdentity: (hostname, cert) => {
// Custom verification logic
if (cert.subject.CN !== hostname) {
return new Error('Certificate hostname mismatch');
}
}
});
Certificate Error Codes
Common errors:
DEPTH_ZERO_SELF_SIGNED_CERT - Self-signed certificate
UNABLE_TO_VERIFY_LEAF_SIGNATURE - Cannot verify certificate
CERT_HAS_EXPIRED - Certificate expired
UNABLE_TO_GET_ISSUER_CERT - Missing CA certificate
HOSTNAME_MISMATCH - Certificate hostname doesn’t match
Session Reuse
Cache and reuse sessions:
let savedSession;
const socket1 = tls.connect(options, () => {
savedSession = socket1.getSession();
socket1.end();
});
socket1.on('close', () => {
// Reuse session
const socket2 = tls.connect({
...options,
session: savedSession
});
});
Connection Pooling
Reuse TLS connections with keep-alive
Cipher Selection
Prefer hardware-accelerated ciphers (AES-GCM on modern CPUs)
Security Best Practices
Always use rejectUnauthorized: true in production. Only disable for testing.
Use TLSv1.2 or TLSv1.3 only. Disable older protocols with minVersion: 'TLSv1.2'.
Enable Perfect Forward Secrecy with ECDHE ciphers (enabled by default).
Regularly rotate session ticket keys to maintain security.
Validate certificates carefully. Use trusted CA certificates and verify hostnames.
Debugging TLS Issues
Enable TLS Debug
NODE_DEBUG=tls node server.js
Test with OpenSSL
# Test server
openssl s_client -connect localhost:8000 -showcerts
# Test session resumption
openssl s_client -connect localhost:8000 -reconnect
# Test specific TLS version
openssl s_client -connect localhost:8000 -tls1_3
Capture Key Log
const logFile = fs.createWriteStream('sslkeys.log', { flags: 'a' });
server.on('keylog', (line) => logFile.write(line));
Use with Wireshark to decrypt traffic for debugging.