This error occurs when Node.js attempts to start an HTTPS server using an encrypted private key file without providing the passphrase needed to decrypt it. The server startup fails because the key cannot be decrypted and used for SSL/TLS operations.
This error indicates that your Node.js application is trying to establish an HTTPS server (or make HTTPS requests) using a private key file that has been encrypted with a passphrase for security. However, your code did not provide the passphrase needed to decrypt this key. Private key files can be password-protected to prevent unauthorized use if the file is compromised. When Node.js reads such a file without the passphrase, it cannot decrypt the key material and therefore cannot proceed with the TLS handshake. This is a security feature - it prevents anyone who gains access to your key file from immediately using it without knowing the passphrase. However, it requires your application to provide the passphrase at runtime.
First, check whether your private key file is actually encrypted:
# Unencrypted key files start with:
# -----BEGIN RSA PRIVATE KEY----- or -----BEGIN PRIVATE KEY-----
# Encrypted key files show:
# -----BEGIN ENCRYPTED PRIVATE KEY----- or contain "Proc-Type: 4,ENCRYPTED"
head -1 your-key.pemIf the first line contains "ENCRYPTED", your key is password-protected.
The most secure way is to provide a passphrase callback function that returns the passphrase. This keeps the passphrase out of environment variables:
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('encrypted-key.pem'),
cert: fs.readFileSync('certificate.pem'),
passphrase: 'your-passphrase' // Can be from env var, config file, or secure storage
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello World');
}).listen(443);For better security, store the passphrase in an environment variable:
const options = {
key: fs.readFileSync('encrypted-key.pem'),
cert: fs.readFileSync('certificate.pem'),
passphrase: process.env.KEY_PASSPHRASE
};For more control, especially in interactive scenarios, use a passphrase callback:
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('encrypted-key.pem'),
cert: fs.readFileSync('certificate.pem'),
passphrase: () => {
// Return passphrase from environment, config, or secure storage
const passphrase = process.env.KEY_PASSPHRASE;
if (!passphrase) {
throw new Error('KEY_PASSPHRASE environment variable is not set');
}
return passphrase;
}
};
https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello World');
}).listen(443);If you don't need the passphrase protection (not recommended for production), you can decrypt the key:
# This will create an unencrypted key file
# WARNING: Only do this if the file is not exposed to untrusted users
openssl rsa -in encrypted-key.pem -out unencrypted-key.pem
# You will be prompted for the original passphrase
# After completion, use unencrypted-key.pem in your Node.js codeThen in your Node.js code, use the unencrypted file without the passphrase option:
const options = {
key: fs.readFileSync('unencrypted-key.pem'),
cert: fs.readFileSync('certificate.pem')
};Ensure your passphrase environment variable is set before starting Node.js:
# Set the environment variable
export KEY_PASSPHRASE="your-actual-passphrase"
# Then start your application
node app.js
# Or in a .env file (load with dotenv package)
KEY_PASSPHRASE=your-actual-passphrase
# Or in package.json scripts
{
"scripts": {
"start": "KEY_PASSPHRASE=your-passphrase node app.js"
}
}Verify it's set:
echo $KEY_PASSPHRASEIf you've lost the original passphrase, generate a new key pair:
# Generate private key with passphrase
openssl genrsa -aes256 -out new-encrypted-key.pem 2048
# When prompted, enter your new passphrase
# This will be used in your Node.js code
# Generate certificate signing request
openssl req -new -key new-encrypted-key.pem -out new-request.csr
# For self-signed certificate
openssl x509 -req -days 365 -in new-request.csr \
-signkey new-encrypted-key.pem -out new-certificate.pemThen use the new key and certificate in your Node.js application with the passphrase you just set.
Passphrase storage best practices:
- Never hardcode passphrases in your source code
- Use environment variables loaded from secure configuration management systems
- In Docker/Kubernetes, use secrets management (Docker Secrets, Kubernetes Secrets, or Vault)
- For local development, use .env files (add to .gitignore) or dotenv package
- In AWS, consider AWS Secrets Manager or Parameter Store
- In production, use HashiCorp Vault or similar secure secret storage
For Express.js applications:
const express = require('express');
const https = require('https');
const fs = require('fs');
const app = express();
const httpsOptions = {
key: fs.readFileSync('encrypted-key.pem'),
cert: fs.readFileSync('certificate.pem'),
passphrase: process.env.KEY_PASSPHRASE
};
https.createServer(httpsOptions, app).listen(443, () => {
console.log('HTTPS server running on port 443');
});Checking key encryption type:
Different key formats require different approaches:
- RSA Private Key (traditional): -----BEGIN RSA PRIVATE KEY-----
- PKCS#8 Format: -----BEGIN ENCRYPTED PRIVATE KEY-----
Both can be encrypted and require the passphrase option.
Testing with curl:
After fixing the error and your server is running:
# Test HTTPS connection (ignore certificate warnings for self-signed)
curl -k https://localhost:443/
# See detailed connection info
curl -kv https://localhost:443/Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams