Keywords: Node.js | SSL certificate verification | certificate chain | ssl-root-cas | HTTPS security
Abstract: This technical article provides an in-depth analysis of the common SSL certificate verification error 'unable to verify the first certificate' in Node.js applications. It explores the fundamental concepts of certificate chains and presents multiple secure solutions, with emphasis on using the ssl-root-cas package for root certificate management, configuring additional CA certificates through environment variables, and avoiding insecure certificate validation bypass methods. Through step-by-step code examples and detailed configuration instructions, developers can effectively resolve SSL certificate verification issues while maintaining application security and reliability.
Problem Background and Error Analysis
When making HTTPS requests in Node.js applications, developers frequently encounter the 'unable to verify the first certificate' error. This error indicates that the client cannot verify the completeness of the SSL certificate chain provided by the server. Unlike web browsers, Node.js does not automatically attempt to complete incomplete certificate chains, making it more stringent about server configuration requirements.
Importance of Certificate Chain Completeness
A standard SSL certificate chain typically consists of three levels: server certificate, intermediate certificate, and root certificate. The server certificate is signed by an intermediate certificate authority, which in turn is signed by a root certificate authority. When servers provide only the server certificate without including necessary intermediate certificates, Node.js clients cannot establish a complete trust chain, leading to verification failures.
Secure Solution Using ssl-root-cas Package
The most recommended solution involves using the ssl-root-cas npm package to manage certificate chains. This package provides a comprehensive collection of root certificates and allows developers to add custom intermediate certificates. Here's the implementation process:
const https = require('https');
const rootCas = require('ssl-root-cas').create();
// Add custom intermediate certificates if needed
rootCas.addFile('/path/to/intermediate.pem');
// Configure global HTTPS agent
https.globalAgent.options.ca = rootCas;
// All subsequent HTTPS requests will use the updated certificate chain
const request = https.get('https://jira.example.com/secure/attachment/206906/update.xlsx', (response) => {
// Process response data
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
console.log('Request completed successfully');
});
});
request.on('error', (error) => {
console.error('Request failed:', error);
});
Environment Variable Configuration Method
Another effective approach involves using the NODE_EXTRA_CA_CERTS environment variable to specify additional CA certificate files. This method is particularly useful for applications that require additional trust certificates:
// Set environment variable when starting the application
// Windows: set NODE_EXTRA_CA_CERTS=C:\path\to\intermediate.pem
// Linux/Mac: export NODE_EXTRA_CA_CERTS=/path/to/intermediate.pem
// Or configure in package.json
{
"scripts": {
"start": "NODE_EXTRA_CA_CERTS=\"/path/to/intermediate.pem\" node app.js"
}
}
Obtaining Missing Intermediate Certificates
When manual retrieval of intermediate certificates is necessary, the OpenSSL toolchain can be utilized:
# Retrieve server certificate information
openssl s_client -connect jira.example.com:443 -servername jira.example.com | tee certificate.log
# Extract issuer information
openssl x509 -in certificate.log -noout -text | grep -i "issuer"
# Download intermediate certificate (using actual URL)
curl --output intermediate.crt http://actual-ca-url/intermediate.crt
# Convert to PEM format
openssl x509 -inform DER -in intermediate.crt -out intermediate.pem -text
Insecure Bypass Methods (Not Recommended)
While setting NODE_TLS_REJECT_UNAUTHORIZED=0 can quickly resolve the issue, this approach completely disables SSL certificate validation and poses significant security risks:
// Not recommended: Disabling SSL validation
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// This makes applications vulnerable to man-in-the-middle attacks
// Should only be used temporarily in development and testing environments
Best Practices Recommendations
To ensure long-term application security, follow these best practices: always use complete certificate chain configurations, regularly update root certificate bundles, avoid any form of certificate validation bypass in production environments, and establish certificate monitoring mechanisms to promptly detect configuration issues.
Integration with Other HTTP Clients
When using popular HTTP clients like axios, custom HTTPS agents can be created:
const axios = require('axios');
const https = require('https');
const rootCas = require('ssl-root-cas').create();
rootCas.addFile('/path/to/intermediate.pem');
const httpsAgent = new https.Agent({
ca: rootCas
});
axios.get('https://jira.example.com/api/endpoint', {
httpsAgent: httpsAgent
})
.then(response => {
console.log('Request successful:', response.data);
})
.catch(error => {
console.error('Request failed:', error);
});
Conclusion
Resolving SSL certificate verification errors in Node.js requires understanding certificate chain mechanics and employing appropriate methods to complete missing certificate information. The ssl-root-cas package offers a secure and flexible solution that ensures applications function correctly across various certificate configuration environments while maintaining high security standards.