Solving TLS Errors in Node.js

Common error messages for TLS/certificate errors

When we are developing a Node.js application (e.g., using the Next.js framework), we might encounter one of the errors related to invalid TLS/SSL certificates:

  • [Error: self-signed certificate]
    { code: 'DEPTH_ZERO_SELF_SIGNED_CERT' }
  • [Error: certificate has expired]
    { code: 'CERT_HAS_EXPIRED' }
  • [Error: self-signed certificate in certificate chain]
    { code: 'SELF_SIGNED_CERT_IN_CHAIN' }
  • [Error: Hostname/IP does not match certificate's altnames: ...]
    { code: 'ERR_TLS_CERT_ALTNAME_INVALID }

They are all somewhat similar in that they are caused by problems with the SSL/TLS certificate served by one of the URLs we try to connect to. They are also relatively easy to reproduce if needed: we have badssl.com, which helps simulate various certificate errors.

Typical cause of those errors

Self-signed certificates are not trusted by default

Typically, those errors occur because we rely on self-signed certificates in development and testing environments.

For instance, if we develop an app and run some service on https://localhost/..., we cannot even get a globally trusted certificate because Public Certificate Authorities (CAs) won’t issue one for localhost (since it is a special hostname not owned by anyone).

Even when we use cloud-based or VM-based environments with domains we own, obtaining globally trusted certificates for development is often a hassle. Even if initiatives like Let’s Encrypt can provide them for free, many people might need to be involved in setting them up in enterprises.

Windows certificate store is not used by Node JS

I also found that even when certificates are trusted in Windows or browsers, NodeJS still needs its own setup for self-signed certificates.

Workarounds

Since this isn’t supposed to be an essay on certificates, let’s jump to workarounds to the above problems:

Option 1: “I’m on a hackathon, and I don’t care about security at all”

The easy option is to tell Node that you don’t care about TLS security at all and set the environment variable:

NODE_TLS_REJECT_UNAUTHORIZED=0

A better name for this variable without double negation could be e.g. “ACCEPT_UNTRUSTED_CERTIFICATES” (and someone proposed it in the past), but due to inertia, we have what we have.

While it often solves most TLS-related errors, some libraries don’t utilize this environment variable and might still throw SELF_SIGNED_CERT_IN_CHAIN errors in some scenarios. In such a case, option 2 is worth trying.

Option 2: “Let’s keep TLS connections secure but add a few exceptions”

To keep TLS validation enabled but expand the list of trusted certificates beyond the standard ones, you can use another environment variable, NODE_EXTRA_CA_CERTS:

# On Windows
NODE_EXTRA_CA_CERTS=c:\SomeFolder\localhost-my-cert.crt 

# On Linux or Linux-based containers it's more like:
NODE_EXTRA_CA_CERTS=/certificates/localhost-my-cert.crt Code language: Bash (bash)

I typically obtain the certificate file by exporting it from Chrome browser (and I export the certificate for the root of the certificate chain). This setting is more secure and elegant than option 1, and doesn’t require much more effort.

Option 3: “I have plenty of free time at work”

Lastly, if your company owns the domain, you can set the enterprise’s wheels in motion and get a globally trusted certificate for your development environments.

While this requires the most time investment, it might be the most cost-effective option if it helps your team avoid TLS problems and dozens of developers looking for workarounds independently. This is especially true if you expose the web service to other teams that might seek support or if you consume it from mobile apps, where workarounds are more challenging to implement.

Good luck!

No comments yet, you can leave the first one!

Leave a Comment