This error occurs when Node.js fails to complete a TLS handshake during an HTTPS or secure WebSocket connection. The client and server cannot agree on compatible encryption protocols, cipher suites, or certificate parameters, causing the secure connection to be rejected.
This error indicates that the TLS (Transport Layer Security) handshake between your Node.js client and the server has failed at a critical negotiation stage. During a TLS handshake, the client and server must mutually agree on encryption parameters including: - TLS protocol version (TLSv1.0, TLSv1.2, TLSv1.3, etc.) - Cipher suites for encryption and authentication - Certificate validity and chain verification - Elliptic Curve Diffie-Hellman (ECDH) parameters When these parameters cannot be agreed upon, the server sends an alert message indicating "handshake failure" and terminates the connection. This is a cryptographic negotiation failure, distinct from certificate validation errors.
First, confirm this is a TLS negotiation issue and not a certificate validation error:
# Test the connection with openssl
openssl s_client -connect example.com:443 -showcerts
# Look at the output:
# - "Verify return code: 0 (ok)" = TLS successful, issue is elsewhere
# - "alert handshake failure" = TLS negotiation failed (this error)
# - "Verify return code: 20" = Certificate validation failed (different error)If you see "alert handshake failure" in the output, the issue is TLS negotiation.
Determine the TLS protocol versions available on the server:
# Test TLSv1.3
openssl s_client -connect example.com:443 -tls1_3 -showcerts 2>&1 | grep -E 'Protocol|Ciphers'
# Test TLSv1.2
openssl s_client -connect example.com:443 -tls1_2 -showcerts 2>&1 | grep -E 'Protocol|Ciphers'
# Test TLSv1.0 (if needed)
openssl s_client -connect example.com:443 -tls1 -showcerts 2>&1 | grep -E 'Protocol|Ciphers'Note which versions work. If only old versions like TLSv1.0 work, you may need special Node.js configuration.
Older Node.js versions may have different default cipher suites and TLS configurations:
# Check your current version
node --version
# Update using nvm (recommended)
nvm install --lts
nvm use --lts
# Or install from nodejs.orgAfter updating, test if the connection works. Node.js frequently patches TLS cipher suite defaults for security.
If updating Node.js doesn't help, explicitly configure TLS parameters in your code:
const https = require('https');
const options = {
hostname: 'example.com',
port: 443,
path: '/api',
method: 'GET',
minVersion: 'TLSv1.2', // Require at least TLS 1.2
maxVersion: 'TLSv1.3', // Allow up to TLS 1.3
ciphers: 'HIGH:!aNULL:!MD5' // OpenSSL cipher string
};
https.request(options, (res) => {
console.log('Connected successfully');
}).on('error', (err) => {
console.error('TLS error:', err.message);
}).end();For Axios or other HTTP libraries:
const axios = require('axios');
const https = require('https');
const agent = new https.Agent({
minVersion: 'TLSv1.2',
ciphers: 'HIGH:!aNULL:!MD5'
});
axios.get('https://example.com/api', { httpsAgent: agent });If the server uses very old TLS versions (TLSv1.0 or TLSv1.1), you may need to lower Node.js's minimum supported version:
# For testing purposes only - NOT for production
node --tls-min-v1.0 your-app.jsThis allows Node.js to negotiate older TLS versions. However, this is a security risk and should only be used for testing. Better solutions:
1. Upgrade the server to support modern TLS
2. Use a proxy that bridges old and new TLS versions
3. Contact the server administrator to update their configuration
In some cases, ECDH (Elliptic Curve Diffie-Hellman) curves cause negotiation failures:
# List curves supported by Node.js
node -e "console.log(require('crypto').getCurves())"
# Test OpenSSL available curves
openssl ecparam -list_curvesIf the server uses a curve not in Node.js's list, explicitly configure compatible curves:
const tls = require('tls');
const https = require('https');
const options = {
hostname: 'example.com',
port: 443,
ecdhCurve: 'auto' // Let Node.js select
};
https.request(options, (res) => {
// Handle response
}).end();Use Node.js built-in TLS debugging to see exactly where negotiation fails:
# Run with TLS debugging enabled
NODE_DEBUG=tls node your-app.js 2>&1 | head -50
# Or with more verbose output
NODE_DEBUG=tls,crypto node your-app.js 2>&1 | head -100The debug output shows:
- Supported cipher suites from both sides
- Selected cipher after negotiation
- TLS version chosen
- Where the handshake fails
This output can help identify the exact incompatibility.
Understanding the TLS handshake sequence:
The TLS handshake involves several message exchanges:
1. Client Hello: Client offers supported TLS versions and ciphers
2. Server Hello: Server selects compatible version and cipher
3. Certificate exchange: Server sends certificate for verification
4. Key exchange: Client and server agree on encryption keys
5. Finished: Both sides confirm handshake success
If the server cannot find a compatible cipher or TLS version at step 2, it sends a "handshake failure" alert and terminates.
Cipher suite compatibility:
Node.js links against OpenSSL, which maintains a list of supported ciphers. If your server uses ciphers removed from recent OpenSSL versions for security reasons (like RC4 or DES), you may need:
# Check OpenSSL version and available ciphers
openssl version
openssl ciphers 'ALL'Modern OpenSSL enforces security levels (0-5, default 2), which can block weak ciphers. You can temporarily lower this for testing:
OPENSSL_CONF=/dev/stdin node -e "..." << 'EOF'
[default]
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
MinProtocol = TLSv1.0
CipherString = DEFAULT@SECLEVEL=1
EOFHowever, this is dangerous and should only be temporary for diagnosis.
Protocol version transitions:
Starting with Node.js 17, TLSv1.0 and TLSv1.1 support was removed by default. If you need to test with these versions:
const options = {
minVersion: 'TLSv1.0', // Explicitly allow older versions
};But immediately contact the server administrator to upgrade their TLS configuration.
Docker and OpenSSL configuration:
When running Node.js in containers, ensure the base image has compatible OpenSSL:
# Alpine: May have older OpenSSL
FROM node:18-alpine
RUN apk add --update openssl
# Or use Debian-based for more recent OpenSSL
FROM node:18-bullseyeDebugging with Wireshark:
For detailed packet-level analysis, use Wireshark to capture the TCP handshake and see exactly where TLS negotiation fails. Filter by "tls" to see the TLS handshake messages and alert codes.
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process