In this tutorial, we will see how to fix the Error: unable to verify the first certificate in nodejs.
I’m trying to download a file from a Jira server using a URL in my NodeJS application, but I’m running into a problem.
The error message I’m getting is:
Error: unable to verify the first certificate in nodejs at Error (native) at TLSSocket.<anonymous> (_tls_wrap.js:929:36) at TLSSocket.emit (events.js:104:17) at TLSSocket._finishInit (_tls_wrap.js:460:8)
When this error occurs
When you connect to a web server and encounter the “unable to verify the first certificate” error, it implies that the server’s certificate chain is incomplete. This usually happens because the server is not configured correctly and fails to include the intermediate certificate in the chain sent to your application.
Certificate Chain Explained
- Server Certificate: This holds a certificate signed by an intermediate authority.
- Intermediate Certificate: This certificate is signed by the root authority.
- Root Certificate: This is a self-signed certificate at the top of the certificate hierarchy.
For a successful SSL/TLS connection, the server should install both the server and intermediate certificates. Root certificates are generally embedded in software applications, browsers, and operating systems. The application serving the certificate must send the complete chain, which includes the server certificate and all intermediates. The root certificate is usually known by the client.
Now, here are the approaches below to fix the issue.
Complete the Certificate Chain
You need to complete the certificate chain yourself. You can do that by following the steps below.
Get the Missing Intermediate Certificate
Now follow the steps below to get the missing intermediate certificate.
Retrieve the Server’s Certificate Details
Use OpenSSL to save the server’s certificate details and identify the issuer (intermediate certificate).
Ensure you have OpenSSL installed (available with Git for Windows).
openssl s_client -connect incomplete-chain.badssl.com:443 -servername incomplete-chain.badssl.com | tee logcertfile
Identify the issuer of the server certificate
The issuer of the server certificate is typically the intermediate certificate.
To find it, run the below command.
openssl x509 -in logcertfile -noout -text | grep -i "issuer"
This command will display the issuer’s details, including the URL of the intermediate certificate.
Download the Intermediate Certificate
Use the URL obtained in the previous step to download the intermediate certificate.
curl --output intermediate.crt http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt
Convert the Certificate to .pem Format
Finally, convert the downloaded certificate to the .pem
format using the below command.
openssl x509 -inform DER -in intermediate.crt -out intermediate.pem -text
Follow these steps to successfully retrieve and prepare the intermediate certificate for use in your applications.
Extend Node’s Certificate Store
Use NODE_EXTRA_CA_CERTS
to include the intermediate certificate in Node’s certificate store.
This can be set in your environment or in the package.json
file.
"start": "cross-env NODE_EXTRA_CA_CERTS=\"path_to_intermediate.pem\" node index.js"
Use the ca Option
Overwrite Node’s built-in root CAs with your own certificate bundle. This requires using a module like ssl-root-cas
to create a custom HTTPS agent configured with your certificate bundle.
You can then use this agent in your Axios requests.
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://incomplete-chain.badssl.com', { httpsAgent }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
Alternatively, you can configure the HTTPS global agent to use this certificate bundle for all requests.
Add the certificate to your NodeJS application
Obtain the SSL
certificate from the Jira
server.
In your NodeJS application, you can use the https
module and include the certificate in the options.
Here is an example of how you might do it:
const https = require('https'); const fs = require('fs'); const options = { hostname: 'your.jira.server', port: 443, path: '/path/to/resource', method: 'GET', ca: fs.readFileSync('path/to/certificate.crt') // Load the certificate }; const req = https.request(options, (res) => { console.log('statusCode:', res.statusCode); console.log('headers:', res.headers); res.on('data', (d) => { process.stdout.write(d); }); }); req.on('error', (e) => { console.error(e); }); req.end();
Use a custom agent with the request
module
If you are using the request
module, you can use a custom agent to include the SSL certificate.
Example
const request = require('request'); const fs = require('fs'); const https = require('https'); const agentOptions = { ca: fs.readFileSync('path/to/certificate.crt') }; const agent = new https.Agent(agentOptions); request({ url: 'https://your.jira.server/path/to/resource', agent: agent }, (error, response, body) => { if (error) { console.error('Error:', error); } else { console.log('Body:', body); } });
Disable SSL validation (Not Recommended)
As a last resort, and only for development or if you are sure about the security implications, you can disable SSL/TLS certificate validation by setting the NODE_TLS_REJECT_UNAUTHORIZED
environment variable to 0
. However, this is generally not recommended as it makes your application vulnerable to MITM (Man-In-The-Middle) attacks.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
Use this approach with caution and only if you understand the risks involved.
Remember, always ensure that your application is secure, especially when dealing with SSL/TLS and certificates. Disabling SSL certificate validation or using self-signed certificates should only be done in controlled environments where security is not a concern, like local testing.