Skip to main content

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

Performance Optimization

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.