How to Fix npm SSL Certificate Errors on macOS: A Complete Troubleshooting Guide

The core question this article answers: Why does npm install throw UNABLE_TO_GET_ISSUER_CERT_LOCALLY or curl: (77) error setting certificate verify locations on macOS — and how do you fix it for good?

The short answer: the root cause is a missing /etc/ssl/cert.pem file on your system. Regenerating it and wiring up NODE_EXTRA_CA_CERTS for Node.js resolves the issue end-to-end. This guide walks through the full diagnostic chain — from the first error message to a fully working runtime.


The Setup: One Package Install, Five Layers of Failure

What started as a routine global package installation:

npm i -g openclaw@2026.3.2

turned into a cascading series of errors: a broken npm registry, a missing SSL certificate file, a Git SSH permission denial, and finally a Node.js runtime that silently timed out on every HTTPS request — without ever mentioning certificates.

This is the nature of macOS development environments. Problems rarely travel alone. Each fix you apply surfaces the next issue waiting underneath.

If you’ve landed here because npm install is throwing SSL errors, or because your Node.js app keeps timing out on HTTPS requests for no obvious reason, you’re in the right place.


Issue 1: npm Registry Override Not Sticking

What this section answers: Why does npm keep hitting registry.npmjs.org even after you’ve set a custom registry?

This was the first hurdle. After running:

npm config set registry https://registry.npmmirror.com

the install error still pointed at the default registry:

npm error request to https://registry.npmjs.org/openclaw failed

The fix is to verify immediately — never assume the config saved:

npm config get registry
# Should return: https://registry.npmmirror.com

If it still shows https://registry.npmjs.org/, the setting didn’t persist. That usually means a project-level .npmrc or an environment variable is overriding your global config.

Permanent fix — edit the global config file directly:

open ~/.npmrc

Add this line:

registry=https://registry.npmmirror.com

Save, then re-run npm config get registry to confirm.

What I learned here: Always validate config changes on the spot. “It should have worked” is not the same as “it did work.” One quick verification check saves minutes of confused debugging.


Issue 2: The Missing CA Certificate File — The Real Root Cause

What this section answers: What does curl: (77) error setting certificate verify locations: CAfile: /etc/ssl/cert.pem CApath: none actually mean, and how do you fix it?

This error means curl cannot locate the system CA certificate bundle at /etc/ssl/cert.pem. Without this file, TLS handshakes fail, and every HTTPS request — from curl, npm, git, and Node.js — breaks down.

Check whether the file exists:

ls -la /etc/ssl/cert.pem

If you see:

ls: /etc/ssl/cert.pem: No such file or directory

That’s your culprit.

Why Does This File Go Missing on macOS?

macOS doesn’t natively depend on /etc/ssl/cert.pem — it uses its own Security Framework and Keychain. This path exists purely for compatibility with Unix-heritage tools like curl, OpenSSL, and Python. It can disappear due to:

  • A major macOS version upgrade resetting the /etc/ssl/ directory
  • Xcode Command Line Tools not being installed or needing a reinstall
  • Switching between Homebrew OpenSSL versions, which handle certificate paths differently
  • Manual system cleanup accidentally removing the file

Fix: Regenerate the Certificate File from macOS Keychain

# Create the directory if it doesn't exist
sudo mkdir -p /etc/ssl

# Export all trusted root certificates from the system Keychain
sudo security find-certificate -a -p \
  /System/Library/Keychains/SystemRootCertificates.keychain \
  | sudo tee /etc/ssl/cert.pem

Verify the fix:

ls -la /etc/ssl/cert.pem
# Expected output: -rw-r--r-- 1 root wheel 235703 ...

# Test with curl
curl https://dashscope.aliyuncs.com/compatible-mode/v1/models \
  -H "Authorization: Bearer YOUR_API_KEY"

A valid JSON response confirms the certificate issue is resolved for curl and system-level tools.

Practical note: This fix applies broadly. Any CLI tool that respects the system CA bundle — curl, wget, git with HTTPS, Python’s urllib — will immediately benefit from this repair.


Issue 3: npm’s Independent SSL Verification

What this section answers: curl works fine now, so why is npm still throwing UNABLE_TO_GET_ISSUER_CERT_LOCALLY?

npm uses Node.js’s TLS implementation, which does not automatically inherit the system /etc/ssl/cert.pem path. It has its own certificate resolution logic. Fixing the system file alone isn’t enough — you need to point npm at it explicitly.

Recommended fix — point npm at the repaired certificate:

npm config set cafile /etc/ssl/cert.pem

This is the safest, most permanent solution. npm will use the certificate file you just rebuilt.

Temporary workaround — disable strict SSL (for emergencies only):

npm config set strict-ssl false
npm i -g openclaw@2026.3.2

# Restore immediately after
npm config set strict-ssl true

⚠️ Do not leave strict-ssl false as a permanent setting. It disables all certificate validation for npm, exposing every package install to potential man-in-the-middle attacks. Use it only to unblock a stuck install, then restore it right away.


Issue 4: Git SSH Dependencies Inside npm Packages

What this section answers: Why would an npm package install trigger a Git SSH error about GitHub?

With npm’s SSL verification resolved, the install made progress — then hit a new wall:

npm error code 128
npm error command git --no-replace-objects ls-remote ssh://git@github.com/whiskeysockets/libsignal-node.git
npm error git@github.com: Permission denied (publickey).

This means one of openclaw‘s transitive dependencies declares its source as an SSH GitHub URL in its package.json. On a machine without a GitHub SSH key configured, that lookup fails immediately.

Fix: Force git to substitute HTTPS for SSH URLs globally:

git config --global url."https://github.com/".insteadOf ssh://git@github.com/

This tells git to transparently rewrite any ssh://git@github.com/ URL to https://github.com/, no SSH key required.

The follow-on problem — git’s own certificate issue:

Switching to HTTPS exposed the same certificate problem in git:

fatal: unable to access 'https://github.com/...': error setting certificate verify locations:
CAfile: /etc/ssl/cert.pem CApath: none

Fix: Point git at the repaired certificate:

git config --global http.sslCAInfo /etc/ssl/cert.pem

This is the clean, permanent solution — more reliable than disabling git’s SSL verification entirely.

Reflection: SSH URLs buried inside transitive npm dependencies are a common but invisible footgun, especially in environments where GitHub SSH keys aren’t set up by default. The insteadOf config is one of those git tricks that’s rarely needed — until suddenly it’s the only thing standing between you and a working install.


Issue 5: Node.js Has Its Own Certificate Isolation

What this section answers: The package installed successfully, but the Node.js app keeps timing out on HTTPS requests — could this still be a certificate problem?

Yes, and this one is the trickiest to diagnose.

After openclaw installed cleanly, launching it with openclaw gateway produced:

LLM request timed out.

The app was configured to use the Alibaba Cloud DashScope API — a domestic Chinese endpoint, no proxy required:

https://dashscope.aliyuncs.com/compatible-mode/v1

curl reached it fine. The API key was valid. The model names were correct. Yet Node.js kept timing out.

The reason: Node.js ships with its own bundled CA certificate list and does not automatically inherit the system CA bundle. When the system certificate file is missing or has been recently regenerated, Node.js processes — including long-running servers and CLI tools — may fail to establish TLS connections, manifesting as timeouts rather than explicit certificate errors.

This makes it particularly hard to diagnose. Nothing in the error message says “certificate.” It just says “timed out.”

Fix: NODE_EXTRA_CA_CERTS

export NODE_EXTRA_CA_CERTS=/etc/ssl/cert.pem

Make it permanent in ~/.zshrc:

echo 'export NODE_EXTRA_CA_CERTS=/etc/ssl/cert.pem' >> ~/.zshrc
source ~/.zshrc

Then relaunch openclaw gateway. The timeouts stop.

How NODE_EXTRA_CA_CERTS works: This environment variable appends the certificates in the specified file to Node.js’s built-in CA list — it does not replace them. Your app gains full trust coverage: Node’s default roots plus any system-specific certificates. It’s the safest way to extend Node.js certificate trust without disabling verification.

Important distinction: NODE_EXTRA_CA_CERTS is safe and appropriate for production use. NODE_TLS_REJECT_UNAUTHORIZED=0 — which fully disables TLS verification in Node.js — is not. Never use the latter outside of local debugging.


Certificate Configuration: Tool-by-Tool Reference

Each major CLI tool on macOS manages certificate trust independently. Here’s a consolidated view:

Tool Certificate Source How to Fix
curl System /etc/ssl/cert.pem Regenerate the file from Keychain
npm Built-in + cafile config npm config set cafile /etc/ssl/cert.pem
git System + http.sslCAInfo config git config --global http.sslCAInfo /etc/ssl/cert.pem
Node.js Built-in bundle NODE_EXTRA_CA_CERTS=/etc/ssl/cert.pem
Python requests certifi package pip install --upgrade certifi or REQUESTS_CA_BUNDLE
Homebrew OpenSSL /opt/homebrew/etc/openssl@3/cert.pem brew install ca-certificates

The key insight: fixing the system certificate file is necessary but not sufficient. Each tool needs to be pointed at that file independently.


Complete Fix: Step-by-Step Execution Order

Run these in sequence. Verify each step before moving to the next.

Step 1: Regenerate the System CA Certificate File

sudo mkdir -p /etc/ssl
sudo security find-certificate -a -p \
  /System/Library/Keychains/SystemRootCertificates.keychain \
  | sudo tee /etc/ssl/cert.pem

# Verify
ls -la /etc/ssl/cert.pem

Step 2: Configure npm

npm config set cafile /etc/ssl/cert.pem
npm config set registry https://registry.npmmirror.com

# Verify
npm config get registry
npm config get cafile

Step 3: Configure Git

git config --global http.sslCAInfo /etc/ssl/cert.pem
git config --global url."https://github.com/".insteadOf ssh://git@github.com/

Step 4: Configure Node.js (Permanent)

echo 'export NODE_EXTRA_CA_CERTS=/etc/ssl/cert.pem' >> ~/.zshrc
source ~/.zshrc

Step 5: Test Everything

# Test curl
curl https://www.google.com

# Test npm install
npm i -g openclaw@2026.3.2

# Test Node.js app
openclaw gateway

Validating an API Key and Endpoint (DashScope Example)

If you’re configuring a Node.js tool against an LLM API and want to verify connectivity independently of the app, use this pattern:

# Single-line curl — avoids shell escaping issues with backslash line breaks
curl "https://dashscope.aliyuncs.com/compatible-mode/v1/models" \
  -H "Authorization: Bearer YOUR_API_KEY_HERE"

A successful response returns a JSON array of available models. If you see a 401 with “You didn’t provide an API key,” the request structure is wrong (check for shell escaping issues). If you see a certificate error, the CA bundle isn’t in place yet.


Author’s Reflection

The thing that surprised me most about this entire chain of errors wasn’t the number of them — it was how differently the same root cause presented across tools.

  • curl said: error setting certificate verify locations
  • npm said: UNABLE_TO_GET_ISSUER_CERT_LOCALLY
  • git said: fatal: unable to access … error setting certificate verify locations
  • Node.js said: LLM request timed out

Three tools at least told me something about certificates. Node.js gave me nothing. That single timeout message could mean a dead server, a firewall, a wrong URL, an invalid API key, a network routing issue — or a missing CA bundle. It took eliminating every other possibility to land on the certificate as the cause.

The practical takeaway: whenever you’re debugging HTTPS failures in a Node.js application and the usual suspects come up empty, run a quick cert check. It’s a five-second sanity test that’s easy to overlook.

The second lesson: verify config changes immediately. Don’t trust that npm config set worked. Don’t trust that git config applied. Check with the corresponding get command right after. A configuration that looks like it saved but didn’t is harder to debug than an error that never went silent.


Quick-Reference Checklist

  • [ ] Verify /etc/ssl/cert.pem exists: ls -la /etc/ssl/cert.pem
  • [ ] Regenerate if missing: sudo security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain | sudo tee /etc/ssl/cert.pem
  • [ ] Point npm at certificate: npm config set cafile /etc/ssl/cert.pem
  • [ ] Set npm registry (if using a mirror): npm config set registry https://registry.npmmirror.com
  • [ ] Point git at certificate: git config --global http.sslCAInfo /etc/ssl/cert.pem
  • [ ] Fix SSH-to-HTTPS for GitHub (if needed): git config --global url."https://github.com/".insteadOf ssh://git@github.com/
  • [ ] Set Node.js cert env var (permanent): echo 'export NODE_EXTRA_CA_CERTS=/etc/ssl/cert.pem' >> ~/.zshrc && source ~/.zshrc
  • [ ] Test curl: curl https://www.google.com
  • [ ] Test npm: npm i -g <package-name>
  • [ ] Test Node.js app behavior after source ~/.zshrc

One-Page Summary

Symptom Root Cause Fix
curl: (77) error setting certificate verify locations /etc/ssl/cert.pem missing Regenerate from Keychain with security find-certificate
npm UNABLE_TO_GET_ISSUER_CERT_LOCALLY npm can’t find trusted CA npm config set cafile /etc/ssl/cert.pem
git: Permission denied (publickey) Dependency uses SSH GitHub URL git config --global url."https://github.com/".insteadOf ssh://git@github.com/
git: error setting certificate verify locations git SSL cert path broken git config --global http.sslCAInfo /etc/ssl/cert.pem
Node.js app request timeout (HTTPS) Node.js certificate isolation export NODE_EXTRA_CA_CERTS=/etc/ssl/cert.pem
npm registry override not sticking Config not persisted globally Edit ~/.npmrc directly, verify with npm config get registry

FAQ

Q: Do I need to restart my terminal after regenerating /etc/ssl/cert.pem?
No — the file change takes effect immediately for curl and system-level tools. However, if you’re adding NODE_EXTRA_CA_CERTS to ~/.zshrc, you’ll need to run source ~/.zshrc or open a new terminal window before it applies to new Node.js processes.

Q: Is it safe to keep npm config set strict-ssl false permanently?
No. This disables all certificate validation for npm, which means every package you install is vulnerable to interception or tampering. Use it only as a temporary unblock during troubleshooting, then immediately restore it with npm config set strict-ssl true.

Q: What’s the difference between NODE_EXTRA_CA_CERTS and NODE_TLS_REJECT_UNAUTHORIZED=0?
NODE_EXTRA_CA_CERTS appends additional trusted certificates to Node.js’s existing bundle — it’s safe and appropriate for production environments. NODE_TLS_REJECT_UNAUTHORIZED=0 completely disables TLS verification, which is a security risk. Never use the latter outside of isolated local debugging.

Q: Why does npm sometimes ignore my custom registry and fall back to the default?
A project-level .npmrc file in your current directory takes precedence over the global config. Also check whether the environment variable NPM_CONFIG_REGISTRY is set, which would override everything. Run npm config list to see all active config sources and their priorities.

Q: Will this fix also help with Python requests SSL errors?
Not directly. Python’s requests library uses certifi‘s bundled certificates, not the system CA bundle. For Python SSL errors, run pip install --upgrade certifi. If you need to point it at the system bundle, set the environment variable REQUESTS_CA_BUNDLE=/etc/ssl/cert.pem.

Q: How do I know if Homebrew’s OpenSSL is causing the conflict instead?
Check whether Homebrew’s cert file exists: ls /opt/homebrew/etc/openssl@3/cert.pem (Apple Silicon) or ls /usr/local/etc/openssl/cert.pem (Intel). If tools installed via Homebrew fail even after the system fix, run brew install ca-certificates to update Homebrew’s cert bundle independently.

Q: Should I expect this problem to come back after future macOS updates?
Possibly, after major version upgrades. The safest habit is to run ls -la /etc/ssl/cert.pem right after any macOS major update. If the file is missing, the regeneration command takes under ten seconds to run. Having NODE_EXTRA_CA_CERTS set in your .zshrc also provides a soft safeguard — as long as the file itself exists, Node.js will use it automatically.

Q: What if I’m on an Intel Mac — do the paths change?
The /etc/ssl/cert.pem path and all the commands in this guide are the same on both Intel and Apple Silicon Macs. The only difference is Homebrew’s OpenSSL path: Intel Macs use /usr/local/etc/openssl/ while Apple Silicon uses /opt/homebrew/etc/openssl@3/.