How to fix Error: SSL_ERROR_BAD_HANDSHAKE (TLS handshake failed) in Node.js

Node.jsINTERMEDIATEHIGHUpdated Dec 9, 2025ยท 183 views

This error occurs when a Node.js client and server cannot establish a secure TLS/SSL connection during the handshake process. The handshake failure typically stems from protocol version mismatches, incompatible cipher suites, certificate validation issues, or misconfigured TLS settings.

Quick fix

Fixes SSL_ERROR_BAD_HANDSHAKE

# Test TLS connection and view certificate details
openssl s_client -connect example.com:443 -servername example.com

# Check supported TLS versions
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# List supported cipher suites
openssl s_client -connect example.com:443 -cipher 'ALL' -servername example.com
  1. Verify the server certificate and TLS configuration
  2. Update Node.js to the latest LTS version
  3. Configure custom CA certificates for self-signed certificates
  4. Specify minimum TLS protocol version
node.js โ€” error
# Test TLS connection and view certificate details
openssl s_client -connect example.com:443 -servername example.com
# Check supported TLS versions
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# List supported cipher suites
openssl s_client -connect example.com:443 -cipher 'ALL' -servername example.com
Error: SSL_ERROR_BAD_HANDSHAKE (TLS handshake failed)

What this error means

The SSL_ERROR_BAD_HANDSHAKE error indicates that Node.js failed to complete the TLS/SSL handshake process when establishing a secure connection. The TLS handshake is a multi-step negotiation where the client and server agree on protocol versions, cipher suites, exchange certificates, and generate encryption keys to create a secure session. When this handshake fails, it means the client and server were unable to find a mutually compatible configuration. This could be due to protocol version incompatibilities (e.g., one side only supports TLS 1.0 while the other requires TLS 1.2+), cipher suite mismatches where no common encryption algorithm exists, certificate validation failures, or network-level issues interrupting the handshake sequence. Node.js has strict security defaults that prefer modern protocols (TLS 1.2+) and strong cipher suites with perfect forward secrecy. While this improves security, it can cause handshake failures when connecting to servers with outdated configurations or when using self-signed certificates without proper configuration.

Typical symptoms

  • HTTPS requests fail immediately during connection establishment
  • Error message contains "SSL_ERROR_BAD_HANDSHAKE" or "TLS handshake failed"
  • Connection works with curl or browsers but fails in Node.js
  • Error occurs when connecting to servers with self-signed certificates
  • Requests timeout during the initial SSL/TLS negotiation phase
  • Works on some systems but fails on others with different Node.js versions
  • Certificate verification errors appear alongside handshake failures

Common causes

  1. Protocol version mismatch between client and server (e.g., server only supports TLS 1.0/1.1 while Node.js requires TLS 1.2+)
  2. No mutually supported cipher suites between client and server
  3. Server using self-signed certificates without proper CA configuration in Node.js
  4. Missing or incomplete certificate chain on the server
  5. System clock significantly different from actual time, causing certificate validity issues
  6. SNI (Server Name Indication) not properly configured on the server
  7. Outdated Node.js version lacking support for modern TLS protocols
  8. Server using weak or deprecated ciphers (RC4, DES) that Node.js rejects by default
  9. ECDH curve incompatibility between client and server
  10. Firewall or proxy interfering with TLS handshake packets

How to fix "Error: SSL_ERROR_BAD_HANDSHAKE (TLS handshake failed)"

1Verify the server certificate and TLS configuration

Use OpenSSL to test the connection and see what protocols and ciphers the server supports:

bash
# Test TLS connection and view certificate details
openssl s_client -connect example.com:443 -servername example.com

# Check supported TLS versions
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# List supported cipher suites
openssl s_client -connect example.com:443 -cipher 'ALL' -servername example.com

Look for protocol version, cipher suite details, and any certificate validation errors in the output.

2Update Node.js to the latest LTS version

Older Node.js versions may lack support for modern TLS protocols and trusted Certificate Authorities:

bash
# Check your current Node.js version
node --version

# Update using nvm (recommended)
nvm install --lts
nvm use --lts

# Or download from nodejs.org
# https://nodejs.org/

After updating, test your connection again. Many handshake issues are resolved by using a current Node.js version.

3Configure custom CA certificates for self-signed certificates

If connecting to a server with self-signed or custom CA certificates, provide the CA certificate to Node.js:

javascript
const https = require('https');
const fs = require('fs');

const options = {
  hostname: 'example.com',
  port: 443,
  path: '/',
  method: 'GET',
  ca: fs.readFileSync('/path/to/ca-cert.pem'),
  // Optionally specify minimum TLS version
  minVersion: 'TLSv1.2',
};

const req = https.request(options, (res) => {
  console.log('Status:', res.statusCode);
  res.on('data', (d) => process.stdout.write(d));
});

req.on('error', (error) => {
  console.error('Error:', error);
});

req.end();

For Axios or other HTTP clients:

javascript
const axios = require('axios');
const https = require('https');
const fs = require('fs');

const agent = new https.Agent({
  ca: fs.readFileSync('/path/to/ca-cert.pem'),
  minVersion: 'TLSv1.2',
});

axios.get('https://example.com', { httpsAgent: agent })
  .then(response => console.log(response.data))
  .catch(error => console.error(error));
4Specify minimum TLS protocol version

If the server supports modern TLS but Node.js is trying older protocols, explicitly set the minimum version:

javascript
const https = require('https');
const tls = require('tls');

const options = {
  hostname: 'example.com',
  port: 443,
  path: '/',
  method: 'GET',
  minVersion: 'TLSv1.2',
  maxVersion: 'TLSv1.3',
};

https.request(options, (res) => {
  console.log('Connected with:', res.socket.getProtocol());
  res.on('data', (d) => process.stdout.write(d));
}).end();

You can also set this globally:

javascript
const tls = require('tls');
tls.DEFAULT_MIN_VERSION = 'TLSv1.2';
5Configure cipher suites if needed

If you need to support specific cipher suites or connect to servers with limited cipher support:

javascript
const https = require('https');

const options = {
  hostname: 'example.com',
  port: 443,
  path: '/',
  method: 'GET',
  ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384',
  honorCipherOrder: true,
};

https.request(options, (res) => {
  console.log('Cipher:', res.socket.getCipher());
  res.on('data', (d) => process.stdout.write(d));
}).end();

To see Node.js default cipher list:

bash
node -e "console.log(require('tls').DEFAULT_CIPHERS)"

Warning: Only modify cipher suites if absolutely necessary. Node.js defaults are secure and well-tested.

6Verify system time and date are correct

Certificate validity is time-sensitive. Incorrect system time can cause handshake failures:

bash
# Check current system time
date

# On Linux, sync with NTP
sudo ntpdate -s time.nist.gov

# Or use systemd-timesyncd
sudo timedatectl set-ntp true
timedatectl status

# On macOS
sudo sntp -sS time.apple.com

After correcting the time, retry your Node.js connection.

7Use system CA certificates

Node.js can use the operating system's certificate store instead of its bundled certificates:

bash
# Run Node.js with system CA certificates
node --use-openssl-ca app.js

# Or set environment variable
export NODE_OPTIONS="--use-openssl-ca"
node app.js

This can help when the server certificate is trusted by your OS but not by Node.js's bundled CA list.

8Enable TLS debugging for detailed diagnostics

Get verbose TLS handshake information to diagnose the exact failure point:

bash
# Enable Node.js TLS debugging
NODE_DEBUG=tls node app.js

# Or use environment variable in code
process.env.NODE_DEBUG = 'tls';

This will output detailed information about:
- Protocol negotiation
- Cipher suite selection
- Certificate exchange
- Exact point of handshake failure

Use this output to identify whether the issue is protocol mismatch, cipher incompatibility, or certificate validation.

Advanced notes

Security Considerations: Never disable certificate validation in production by setting rejectUnauthorized: false or NODE_TLS_REJECT_UNAUTHORIZED=0. This completely undermines the security provided by TLS and makes your application vulnerable to man-in-the-middle attacks. If you need to accept self-signed certificates, always use the ca option to explicitly trust specific certificates.

Protocol Evolution: TLS 1.0 and 1.1 are deprecated as of 2020 and disabled by default in modern Node.js versions (12+). If you must connect to legacy servers, you can set minVersion: 'TLSv1', but be aware of the security implications. Consider upgrading the server instead.

SNI (Server Name Indication): When connecting via HTTPS, Node.js automatically sends the SNI extension with the hostname. For IP-based connections, you may need to manually specify the servername in options: servername: 'example.com'. This is crucial for servers hosting multiple SSL certificates.

Perfect Forward Secrecy: Node.js prefers cipher suites with PFS (ECDHE, DHE) which ensure that past sessions remain secure even if the server's private key is later compromised. Some legacy systems don't support PFS ciphers, causing handshake failures.

Certificate Chain Issues: If the server doesn't send the complete certificate chain (intermediate certificates), Node.js may fail to validate the certificate. The server administrator needs to configure the full chain. You cannot fix this client-side without disabling validation.

Docker and Container Issues: In containerized environments, ensure CA certificates are present in the container image. Alpine-based images may need: apk add --no-cache ca-certificates. Also verify that the container's system time is synchronized with the host.

Proxy and Corporate Environments: Corporate proxies often perform TLS inspection (MITM), which requires installing the proxy's CA certificate. Use the ca option or system CA store to trust the proxy certificate.

HTTP/2 and ALPN: When using HTTP/2 over TLS, ensure both client and server support ALPN (Application-Layer Protocol Negotiation). Node.js supports this automatically, but some older servers may not.

Frequently asked questions

What does "SSL_ERROR_BAD_HANDSHAKE" mean?
js failed to complete the TLS/SSL handshake process when establishing a secure connection. The TLS handshake is a multi-step negotiation where the client and server agree on protocol versions, cipher suites, exchange certificates, and generate encryption keys to create a secure session. When this handshake fails, it means the client and server were unable to find a mutually compatible configuration.
How do I fix "SSL_ERROR_BAD_HANDSHAKE"?
This error occurs when a Node.js client and server cannot establish a secure TLS/SSL connection during the handshake process. The handshake failure typically stems from protocol version mismatches, incompatible cipher suites, certificate validation issues, or misconfigured TLS settings.
What causes "SSL_ERROR_BAD_HANDSHAKE"?
Protocol version mismatch between client and server (e.g., server only supports TLS 1.0/1.1 while Node.js requires TLS 1.2+) No mutually supported cipher suites between client and server Server using self-signed certificates without proper CA configuration in Node.js
Is the "SSL_ERROR_BAD_HANDSHAKE" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading