Skip to content

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', the SSL_OP_NO_* flags for older protocols are set automatically. The SSL_OP_NO_COMPRESSION flag 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();
});

Related Guides

View all Infrastructure guides →

Configured TLS? Now Monitor It.

Generator Labs alerts you before certificates expire, get revoked, or fail chain validation, across HTTPS, SMTPS, IMAPS, LDAPS, and more.

Certificate Monitoring →