Give each runtime your private CA's certificate in its own way. Node reads a PEM file named by NODE_EXTRA_CA_CERTS, curl takes a cacert option, Python requests honors REQUESTS_CA_BUNDLE, and Java needs the certificate imported with keytool. Never switch verification off to avoid the work.
The shared idea
A client trusts a server when its certificate chains to a root the client holds. A private CA is not in public trust stores, so you add its root certificate, never its key, to the trust list of every runtime that must connect. Each runtime keeps its own list, which is why one fix rarely covers all of them.
Per runtime
| Runtime | How to trust the CA |
|---|---|
| Node.js | Set NODE_EXTRA_CA_CERTS to a PEM file |
| Python requests | Set REQUESTS_CA_BUNDLE or pass a bundle path |
| Java | Import into a truststore with keytool |
| curl | Pass a CA file for one transfer, or set CURL_CA_BUNDLE |
Node.js. The documentation says the well known root CAs are extended with the extra certificates in the file, which should hold one or more trusted certificates in PEM format. A missing or malformed file produces a single warning, and errors are otherwise ignored, so check the warning. If your code sets the ca option explicitly on a TLS call, neither the well known nor the extra certificates are used. Newer Node releases can also load the operating system store when you set NODE_USE_SYSTEM_CA, added in v24.6.0 and v22.19.0.
Python. Requests reads REQUESTS_CA_BUNDLE from the environment. One gotcha from its own documentation: when you build requests with a prepared Session flow, environment settings are not merged automatically, and you get a certificate verify failure unless you merge them.
Java. keytool imports a certificate or chain with the importcert command, into a truststore you then point the JVM to.
keytool -importcert -alias corp-root -file corp-root.pem -keystore truststore.p12
curl. Use a CA file for a single transfer, or set CURL_CA_BUNDLE, SSL_CERT_FILE or SSL_CERT_DIR. On some platforms curl can use the native store.
curl --cacert corp-root.pem https://internal.example.com
What not to do
curl's own documentation strongly recommends avoiding the insecure option even for experiments. Node says NODE_TLS_REJECT_UNAUTHORIZED set to 0 disables validation, making TLS insecure, and strongly discourages it. Both leave you open to interception. The safer pattern is in what to do instead of disabling verification.
Make it maintainable
- Distribute the CA certificate in one place, such as a base image layer.
- Scope the CA with name constraints so a leaked key cannot impersonate public sites. See name constraints for a private CA.
- Rotate the CA before it expires and document where it is installed.
Where Safeguard fits
Safeguard scans dependencies and SBOMs for known vulnerabilities, including those in the TLS and crypto libraries your projects pull in, and you can try it with the free scan at gold.safeguard.sh/scan.
Quick check
Should I share the CA's private key with developers?
No. Share the CA certificate, which is public. The key stays offline and protected.
Does NODE_EXTRA_CA_CERTS replace the built in roots?
No. It extends them, unless your code sets the ca option explicitly.
Sources
- Node.js, Command line options
- curl, SSL CA Certificates
- Requests, Advanced usage
- Oracle, keytool documentation
Last reviewed: 19 October 2026.