Fix npm UNABLE_TO_GET_ISSUER_CERT_LOCALLY and SELF_SIGNED_CERT_IN_CHAIN safely
npm reports UNABLE_TO_GET_ISSUER_CERT_LOCALLY or SELF_SIGNED_CERT_IN_CHAIN when Node.js cannot build a trusted certificate path for the configured registry or an HTTPS-intercepting proxy. This guide restores an explicit trust chain without disabling TLS verification.
Identify which registry and certificate chain npm is actually using, obtain the correct corporate or private CA through an authenticated channel, configure the narrowest appropriate trust path, and prove that registry access works with strict TLS verification enabled.
- Node.js 22 LTS, 24 LTS, current supported releases
- npm CLI 10.x, 11.x, 12.x
- Operating systems Linux, macOS, Windows
- Authorized trust material Obtain the organization root and required intermediate CA certificates from the security, PKI, or network team through an authenticated channel. Never copy a certificate merely because it appeared in an intercepted connection.
Confirm the expected SHA-256 fingerprints with the CA owner before changing npm trust. - Current configuration access Use the same account, shell, Node.js binary, npm binary, working directory, and CI identity that reproduce the error; npm configuration is layered by project, user, global, environment, and command-line scope.
node --version && npm --version && npm config get userconfig && npm config get globalconfig - Registry identity Know whether the failing request targets the public npm registry, a scoped private registry, a repository manager, or a tarball host referenced by the lockfile.
npm config get registry - Change record and recovery path Record the effective values you will change and preserve the existing npmrc outside the project repository. Do not copy authentication tokens into tickets or shell history.
npm config get cafile && npm config get strict-ssl - OpenSSL or equivalent inspection OpenSSL is used only to inspect the peer chain and the supplied CA bundle. Windows operators may use an approved OpenSSL build or the certificate inspection tools provided by their organization.
openssl version
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A repeatable evidence path that separates registry selection, network routing, peer certificate identity, Node/npm trust configuration, and dependency resolution.
- A minimal CA bundle whose provenance and fingerprints are confirmed by the PKI owner instead of copied from an untrusted connection.
- An npm configuration that keeps strict-ssl enabled and points only the intended identity at the reviewed CA file.
- A cross-platform decision for npm cafile, managed operating-system trust, or a process-scoped NODE_EXTRA_CA_CERTS setting.
- A verification record covering registry ping, package metadata, tarball host, a script-disabled dry run, and clean rollback.
- Explain why UNABLE_TO_GET_ISSUER_CERT_LOCALLY and SELF_SIGNED_CERT_IN_CHAIN identify trust-path failures rather than registry authentication failures.
- Identify the exact registry, scope, tarball host, proxy path, npmrc layers, Node runtime, and user context involved.
- Inspect the certificate chain without treating network-delivered certificates as automatically trustworthy.
- Reject common bypasses such as strict-ssl=false, NODE_TLS_REJECT_UNAUTHORIZED=0, HTTP registries, and trusting a leaf certificate.
- Configure an approved PEM CA bundle through npm's documented cafile setting while preserving strict validation.
- Decide when a Node process also needs NODE_EXTRA_CA_CERTS or supported system-CA behavior.
- Verify the repair with read-only registry operations before a bounded install canary.
- Roll back the exact configuration change and preserve a fail-closed state if validation regresses.
Architecture
How the parts fit together
npm TLS succeeds only when routing reaches the intended registry, the peer presents an identity for that hostname, Node/npm can build its certificate path to an approved trust anchor, and later metadata or tarball requests stay within the reviewed boundary.
- The failing npm process resolves its effective registry, scoped registry, proxy, strict-ssl, and cafile settings.
- DNS and routing deliver the request to the registry or an authorized interception service selected for that identity and network.
- The peer presents a leaf certificate and any intermediates it chooses to send.
- Node/npm checks the hostname and attempts to build a valid path to a configured trust anchor.
- If an issuer is missing or untrusted, npm raises UNABLE_TO_GET_ISSUER_CERT_LOCALLY or SELF_SIGNED_CERT_IN_CHAIN before registry application logic can complete.
- The operator authenticates the correct private root and intermediate chain through the PKI owner, not through the failing path.
- npm loads the reviewed cafile while strict-ssl remains true; a fresh Node process loads any separately approved extra or system CA setting.
- Read-only ping and metadata requests prove the control plane, and a script-disabled dry run exercises artifact resolution.
- Monitoring and ownership keep the trust chain valid through certificate, Node, runner-image, and network changes.
Assumptions
- The registry and any interception service are authorized by the organization and their hostnames are known.
- The operator can obtain CA fingerprints through an authenticated second channel.
- The Node.js and npm releases are supported by their maintainers and organization policy.
- The failing workflow can be reproduced without executing arbitrary package lifecycle scripts.
- Package-manager tokens and proxy credentials are stored through an approved secret mechanism.
- The guide does not authorize bypassing network controls, importing an unknown CA, or changing a public registry to HTTP.
- Windows, macOS, Linux, containers, and CI runners may have different system trust stores even when npm configuration is similar.
- A private CA rollout has an owner, renewal process, and recovery method.
Key concepts
- UNABLE_TO_GET_ISSUER_CERT_LOCALLY
- Node/OpenSSL could not find enough trusted issuer material locally to complete the peer certificate path. Typical causes include a missing intermediate, private CA, intercepted chain, wrong endpoint, or incorrect CA configuration.
- SELF_SIGNED_CERT_IN_CHAIN
- The evaluated path contains a self-signed certificate that is not accepted as a configured trust anchor. It may be an organization root, a private registry root, or an unexpected substitution.
- Trust anchor
- A CA certificate deliberately trusted as the root of a validated chain. Trust must come from an authenticated policy or PKI source, not merely from the server that failed validation.
- Intermediate CA
- A CA certificate between the leaf and root. Servers normally send required intermediates, while clients already hold the trusted root.
- Leaf certificate
- The end-entity certificate for the registry hostname. Trusting one leaf as a root is brittle and masks incorrect chain deployment.
- cafile
- npm configuration path to a PEM file containing one or more CA signing certificates used for registry TLS validation.
- strict-ssl
- npm setting that requires certificate validation for HTTPS registry requests. The secure state is true.
- NODE_EXTRA_CA_CERTS
- Node environment variable naming a PEM file that extends default CA trust when set before the process starts. It is broader than an npm-only cafile setting.
- System CA mode
- A supported Node option that includes operating-system trust. Availability and behavior depend on Node version and platform.
- TLS interception
- An authorized network service terminates outbound TLS and issues a replacement certificate from an organization CA. Its use requires explicit policy, protected CA distribution, and user awareness.
- Configuration precedence
- npm chooses values from several scopes. A project or environment setting can override a correctly edited user configuration.
- Registry scope
- An npm package scope can select its own registry, so public and private packages may use different trust paths.
- Fail closed
- When trust cannot be validated, the package operation stops instead of silently disabling verification or using an insecure transport.
Fill these once. Every matching command and configuration block updates immediately; values stay in this page only.
Security and production boundaries
- Do not set strict-ssl=false. npm's own documentation recommends configuring the CA or CA file for an intercepting proxy instead of disabling SSL protection.
- Do not set NODE_TLS_REJECT_UNAUTHORIZED=0. It disables certificate verification for Node TLS clients in that process environment.
- Do not change the registry from HTTPS to HTTP.
- Do not trust a certificate copied from openssl s_client, a browser export, email attachment, wiki page, or ticket unless its fingerprint is authenticated against the PKI owner.
- Do not place CA private keys, registry tokens, proxy passwords, or full npmrc content in the trust bundle or incident record.
- A CA certificate is public but security-sensitive because trusting it grants authority. Protect its distribution and replacement path.
- Use least-privilege read tokens for private registry verification and remove them from diagnostics.
- Keep lifecycle scripts disabled during the connectivity canary; certificate repair does not reduce package supply-chain risk.
- Treat an unexpected registry, issuer, DNS destination, proxy, or certificate fingerprint as a potential security incident.
- Scope trust changes to the smallest identity and client. npm cafile is narrower than a global operating-system trust change.
- Restart Node processes after changing NODE_EXTRA_CA_CERTS because Node reads it only at process start.
- Monitor private CA and intermediate expiry before it breaks CI or developer workflows.
Stop before continuing if
- The certificate hostname does not match the registry, has invalid dates, or is issued by an unknown organization.
- The CA fingerprint cannot be confirmed through an authenticated second channel.
- The registry, scoped registry, tarball host, proxy, DNS answer, or redirect is unexpected.
- The proposed fix requires strict-ssl=false, NODE_TLS_REJECT_UNAUTHORIZED=0, an HTTP registry, or ignoring certificate errors.
- The CA bundle contains a private key, an unexplained certificate, a weak or expired CA, or is writable by an untrusted identity.
- The project or global npmrc contains credentials, tokens, or proxy passwords that may have been exposed.
- The failing system uses an unsupported Node/npm release whose trust behavior is outside the reviewed compatibility range.
- npm ping succeeds but installation reaches another unreviewed host.
- The canary resolves an unexpected package, version, Git source, or large dependency change.
- The private registry cannot provide a coherent chain and its owner cannot confirm the intended CA.
- Storage, clock, DNS, or routing evidence indicates a different root cause.
- An operator cannot preserve or restore the exact configuration scope being changed.
command
Capture the exact error and effective npm context
Reproduce the failure once with the same user, directory, Node.js runtime, npm binary, and registry scope as the affected workflow. Preserve the first TLS error, request host, npm debug-log path, and non-secret configuration values. UNABLE_TO_GET_ISSUER_CERT_LOCALLY means the presented path cannot be completed to a trusted issuer; SELF_SIGNED_CERT_IN_CHAIN means an untrusted self-signed certificate is present in the evaluated chain. Both are trust-path failures, not permission to bypass verification.
Why this step matters
Configuration scope and runtime identity explain why a browser or another shell can succeed while npm fails. Capturing them before editing prevents a misleading global workaround.
What to understand
Run the command from the failing project because a project .npmrc can override user and global configuration.
Do not paste npm config list output into a ticket: registry-scoped authentication entries and proxy URLs can contain credentials even when some values are masked.
Preserve the first error and request URL from the npm debug log, then redact usernames, tokens, query strings, and internal hostnames before sharing evidence.
If CI fails, collect the Node and npm versions, working directory, configured registry hostname, cafile path, and strict-ssl state from the job itself rather than from a developer laptop.
System changes
- None. These commands read version and selected npm configuration values.
Syntax explained
npm config get <key>- Resolves one configuration key through npm's normal precedence without dumping unrelated authentication settings.
userconfig / globalconfig- Shows which files npm considers its user and global configuration sources.
strict-ssl- Controls whether npm verifies registry TLS certificates; the secure required value is true.
cafile- Names a PEM file containing one or more trusted CA certificates for npm registry connections.
node --version && npm --version && npm config get registry && npm config get userconfig && npm config get globalconfig && npm config get strict-ssl && npm config get cafilev24.6.0 11.5.2 https://registry.npmjs.org/ /home/operator/.npmrc /usr/local/etc/npmrc true null npm error code UNABLE_TO_GET_ISSUER_CERT_LOCALLY npm error request to https://registry.npmjs.org/example failed, reason: unable to get local issuer certificate
Checkpoint:
Continue when
Stop when
If this step fails
The error cannot be reproduced interactively
Likely causeCI, a service account, a project .npmrc, or a different Node installation uses another environment.
npm config get userconfignpm config get globalconfignode -p process.execPath
ResolutionCollect evidence inside the failing process context and compare only sanitized configuration keys.
Security notes
- Treat registry tokens, proxy credentials, internal hostnames, and npm debug logs as sensitive.
- Do not change strict-ssl while diagnosing.
Alternatives
- Use npm config get <key> --location=project, user, or global to compare one scope at a time when supported by the installed npm version.
Stop conditions
- Stop if strict-ssl is already false; document the insecure state and restore verified TLS before any further package operation.
- Stop if the request URL contains embedded credentials or an unexpected registry. Rotate exposed credentials and correct routing first.
command
Identify the registry, scope, proxy, and lockfile hosts
Determine which hostname actually fails. An unscoped package uses the default registry, a scoped package may use a scope-specific registry, and an existing lockfile may refer to a registry or tarball host that differs from the current default. A TLS fix applied to the wrong host gives false confidence.
Why this step matters
npm can validate the registry but later fail on a different tarball host. Inventorying hosts bounds the certificate and proxy review.
What to understand
Use a real scope without the leading @ for packageScope, for example team.
Review package-lock.json as data; do not rewrite it merely to test TLS.
Confirm every host belongs to the expected public registry, private registry, or authorized artifact service.
Check project, user, and global npmrc files for registry, scoped registry, cafile, strict-ssl, proxy, and https-proxy keys, but redact credentials before recording them.
System changes
- None. The Node snippet reads package-lock.json and prints unique resolved hostnames.
Syntax explained
@{{packageScope}}:registry- Reads the registry selected for one npm package scope.
p.packages- Examines lockfile package records without installing or modifying dependencies.
new URL(...).host- Extracts only the destination host from each resolved URL, avoiding paths and query parameters in the inventory.
Values stay on this page and are never sent or saved.
npm config get registry && npm config get @{{packageScope}}:registry && node -e "const fs=require('fs');if(fs.existsSync('package-lock.json')){const p=JSON.parse(fs.readFileSync('package-lock.json','utf8'));const h=[...new Set(Object.values(p.packages||{}).map(x=>x&&x.resolved).filter(Boolean).map(x=>{try{return new URL(x).host}catch{return null}}).filter(Boolean))];console.log(h.join('\n')||'no resolved hosts')}else console.log('no package-lock.json')}"https://registry.npmjs.org/ https://packages.example.net/npm/team/ registry.npmjs.org packages.example.net
Checkpoint:
Continue when
Stop when
If this step fails
npm config get @scope:registry returns undefined
Likely causeThe scope uses the default registry or is configured in another user or CI context.
npm config get registrynpm config get userconfig
ResolutionInspect the exact failing context and do not invent a private registry mapping.
Security notes
- Do not print complete .npmrc files or resolved URLs when they may contain credentials.
- An unexpected registry or tarball host is a supply-chain incident until explained.
Alternatives
- For a single package, npm view {{packageName}} dist.tarball --json can identify its tarball URL, but treat the output as network evidence and redact private paths.
Stop conditions
- Stop if a lockfile points to an unapproved host, plain HTTP, an IP literal, or a domain with unclear ownership.
- Stop if registry or proxy credentials appear in project files or logs; remove and rotate them before proceeding.
command
Inspect the certificate chain presented to Node
Connect to the exact failing hostname with Server Name Indication and display verification output. Compare the issuer and SHA-256 fingerprint with the registry operator or corporate PKI documentation. A corporate TLS inspection service may intentionally replace the public chain, but it is trusted only after its CA is authenticated out of band.
Why this step matters
The peer chain reveals whether the problem is a missing intermediate, a private registry certificate, authorized TLS interception, an unexpected endpoint, or a stale trust store.
What to understand
Use the hostname, not an IP address, so SNI selects the same certificate npm receives.
A successful browser connection does not prove Node uses the same trust store.
Repeat from the failing CI runner or host because egress paths and interception policies can differ.
Do not automatically save and trust the certificate printed by s_client. The network path that causes the error could also substitute an attacker-controlled certificate.
System changes
- None. The command opens a TLS connection and prints selected certificate-verification evidence.
Syntax explained
-showcerts- Displays certificates sent by the peer; it does not establish that any of them are trustworthy.
-verify_return_error- Stops verification at the first certificate validation error instead of continuing silently.
-servername {{registryHost}}- Sends the expected TLS SNI hostname.
</dev/null- Closes standard input so the diagnostic connection does not remain interactive.
Values stay on this page and are never sent or saved.
openssl s_client -showcerts -verify_return_error -connect {{registryHost}}:443 -servername {{registryHost}} </dev/null 2>&1 | sed -n '/depth=/p;/subject=/p;/issuer=/p;/Verify return code/p;/verify error/p'depth=0 CN = registry.npmjs.org verify error:num=20:unable to get local issuer certificate subject=CN = registry.npmjs.org issuer=O = Example Inspection PKI, CN = Example Issuing CA Verify return code: 20 (unable to get local issuer certificate)
Checkpoint:
Continue when
Stop when
If this step fails
openssl shows a different certificate than the browser
Likely causeDifferent proxy, VPN, DNS answer, IPv4/IPv6 path, container network, or trust behavior.
npm config get proxynpm config get https-proxy
ResolutionCompare the actual egress path and proxy policy for the failing runtime; do not merge evidence from different paths.
Security notes
- Authenticate CA fingerprints through a second trusted channel.
- Never trust a leaf certificate as a root merely to make one request pass.
Alternatives
- On Windows, use organization-approved certificate inspection and PowerShell tools if OpenSSL is unavailable; preserve subject, issuer, validity, SAN, and SHA-256 fingerprint.
Stop conditions
- Stop if the certificate hostname is wrong, validity dates are invalid, the issuer is unknown, or the fingerprint disagrees with the PKI owner.
- Stop if DNS or proxy routing reaches an unexpected endpoint.
command
Test the registry with current trust unchanged
Use npm's read-only registry ping to confirm that the problem occurs in npm itself and is tied to the selected registry. Do not install packages yet. The expected failure is evidence; repeated retries will not repair a missing CA.
Why this step matters
npm ping exercises the configured registry connection without modifying a dependency tree or running lifecycle scripts.
What to understand
Run with the explicit registry URL to avoid testing a different default.
Capture only the lines needed to identify the error, registry, Node/npm versions, and log file; redact authorization and proxy information.
A timeout or DNS error is a different incident from certificate validation and should be diagnosed separately.
If ping succeeds but install fails, inspect tarball hosts, Git dependencies, and lifecycle network calls instead of changing the registry CA.
System changes
- npm may write a local debug log or refresh harmless cache metadata; it does not change project dependencies.
Syntax explained
npm ping- Calls the registry ping endpoint and reports connectivity or authentication status.
--registry={{registryUrl}}- Pins the test to the exact reviewed registry.
--loglevel=verbose- Adds request context for diagnosis; the resulting log must be treated as sensitive.
Values stay on this page and are never sent or saved.
npm ping --registry={{registryUrl}} --loglevel=verbosenpm verbose title npm ping npm notice PING https://registry.npmjs.org/ npm error code SELF_SIGNED_CERT_IN_CHAIN npm error request to https://registry.npmjs.org/-/ping failed, reason: self-signed certificate in certificate chain
Checkpoint:
Continue when
Stop when
If this step fails
npm ping succeeds but npm install fails
Likely causeA package tarball host, private scope, Git dependency, or lifecycle script uses another endpoint.
npm view {{packageName}} dist.tarball --json --registry={{registryUrl}}npm config get @{{packageScope}}:registry
ResolutionIdentify and inspect the failing host before extending trust.
Security notes
- Do not add --strict-ssl=false, NODE_TLS_REJECT_UNAUTHORIZED=0, or an HTTP registry to this test.
- Verbose logs can contain internal URLs and request metadata.
Alternatives
- npm view {{packageName}} version --registry={{registryUrl}} is another read-only registry request when the ping endpoint is not supported by a private registry.
Stop conditions
- Stop if the registry responds over HTTP, redirects to an unapproved domain, or requests credentials unexpectedly.
command
Validate the approved CA bundle before trusting it
Receive the root and any required intermediate certificates from the registry operator or organizational PKI team. The file must be PEM encoded, readable by the runtime identity, contain only the intended trust anchors, and match fingerprints confirmed through an authenticated channel. A certificate copied from the failing TLS session is evidence, not automatically an authority.
Why this step matters
A syntactically valid PEM file can still contain the wrong, expired, weak, or overly broad certificate. Review the identities and validity before npm relies on it.
What to understand
Prefer a minimal organization-approved bundle rather than concatenating unrelated roots.
Confirm each root or intermediate SHA-256 fingerprint against the PKI inventory or a signed delivery record.
Ensure CA certificates have appropriate CA constraints and are currently valid.
Set filesystem permissions so the Node/npm identity can read the bundle while unprivileged users cannot replace it.
Keep private keys out of the bundle. A trust bundle contains public certificates only.
System changes
- None. These commands parse the supplied file and print certificate metadata.
Syntax explained
crl2pkcs7 -nocrl -certfile- Wraps PEM certificates for enumeration without adding a revocation list.
pkcs7 -print_certs -noout- Lists subjects and issuers for every certificate in the bundle.
pkcs7 -print_certs -text- Shows validity, constraints, algorithms, and other X.509 details for review.
Values stay on this page and are never sent or saved.
test -r "{{caBundle}}" && openssl crl2pkcs7 -nocrl -certfile "{{caBundle}}" | openssl pkcs7 -print_certs -noout && openssl crl2pkcs7 -nocrl -certfile "{{caBundle}}" | openssl pkcs7 -print_certs -text -noout | grep -E 'Subject:|Issuer:|Not Before:|Not After :|CA:TRUE|Signature Algorithm' subject=O = Example Corporation, CN = Example Root CA
issuer=O = Example Corporation, CN = Example Root CA
subject=O = Example Corporation, CN = Example Issuing CA
issuer=O = Example Corporation, CN = Example Root CA
CA:TRUE
Not After : Aug 22 12:00:00 2031 GMTCheckpoint:
Continue when
Stop when
If this step fails
OpenSSL cannot parse the file
Likely causeThe file is DER, truncated, contains non-certificate text, or has malformed PEM boundaries.
file {{caBundle}}openssl x509 -in {{caBundle}} -noout -subject -issuer
ResolutionRequest a clean PEM CA bundle from the authorized issuer; do not hand-edit unknown certificate data.
Security notes
- A new trusted root can authenticate any hostname within its policy. Treat this as a security-sensitive change.
- Do not download a CA from the same untrusted connection that is failing validation.
Alternatives
- When policy requires the operating-system trust store, have platform administrators deploy the CA through managed Linux trust, macOS Keychain, or Windows certificate policy, then use a supported Node system-CA mode where available.
Stop conditions
- Stop if any fingerprint, subject, issuer, validity period, key usage, or source is unexplained.
- Stop if the file contains a private key marker or is writable by the service account.
command
Configure npm to use the reviewed CA bundle
Point npm at the approved PEM bundle in the narrowest configuration scope that covers the failing workflow. Keep strict-ssl true. User scope is appropriate for a developer account or dedicated service identity; a project setting may be appropriate only when the referenced path is portable and the repository does not contain private PKI material.
Why this step matters
npm's documented cafile setting adds the required CA material while retaining certificate validation. The explicit strict-ssl check prevents an old insecure override from hiding the real result.
What to understand
Record the previous cafile value before changing it.
Use an absolute path that exists for the actual developer, CI runner, service, or container identity.
Do not commit the organization's CA bundle unless policy explicitly classifies and distributes it that way; prefer managed installation.
If a project .npmrc overrides cafile or strict-ssl, resolve the precedence intentionally rather than adding conflicting global settings.
For ephemeral CI, provision the CA as a protected file or image layer and set npm config for that job. Never store it in an untrusted artifact.
System changes
- Updates the current user's npm configuration file.
- Subsequent npm registry connections made by this user can trust the certificates in the configured bundle.
Syntax explained
cafile- Sets the path to a file containing one or more CA signing certificates.
--location=user- Writes the setting to the npm user configuration instead of silently modifying the project or global installation.
strict-ssl true- Requires npm to verify registry TLS certificates.
Values stay on this page and are never sent or saved.
npm config set cafile "{{caBundle}}" --location=user && npm config set strict-ssl true --location=user && npm config get cafile && npm config get strict-ssl/etc/company/pki/node-ca-bundle.pem true
Checkpoint:
Continue when
Stop when
If this step fails
npm config get cafile prints a different path
Likely causeA project, environment, command-line, or alternate user configuration overrides the user setting.
npm config get userconfignpm config get globalconfignpm config get cafile --location=project
ResolutionFind the overriding scope and make one reviewed change there; do not stack contradictory settings.
Security notes
- Never use npm config set strict-ssl false as a workaround.
- Do not embed certificate content, private keys, registry tokens, or proxy passwords in a command line.
Alternatives
- Set cafile in a dedicated CI npmrc supplied as a protected file, then select it with NPM_CONFIG_USERCONFIG for the job.
- Use a managed system trust deployment plus a Node version and system-CA mode supported by your organization when npm and all child tools must share platform trust.
Stop conditions
- Stop if the configuration file is shared by unrelated identities or writable by an untrusted user.
- Stop if the only available CA came from an unverified source.
instruction
Handle Node processes and explicit proxies without widening trust
npm cafile applies to npm registry operations, but application code, package-manager subprocesses, Git dependencies, or lifecycle tools may use Node's trust configuration independently. Node documents NODE_EXTRA_CA_CERTS as a PEM file loaded when the process starts. Configure it only for the service or CI job that needs the organization CA. Configure an npm proxy only when the network team has provided an explicit authorized proxy URL; transparent TLS inspection usually needs a CA, not an invented proxy.
Why this step matters
Separating npm trust, Node process trust, and routing prevents a registry fix from silently changing every TLS client or sending traffic through an unapproved proxy.
What to understand
NODE_EXTRA_CA_CERTS extends the well-known roots with PEM certificates and is read only when a Node process starts.
Changing process.env.NODE_EXTRA_CA_CERTS inside an already-running application does not change that process's trust.
Node ignores extra CAs when application code explicitly supplies its own ca option; repair that application's documented configuration instead.
Recent Node releases can use the system CA store through documented command-line or environment options, but version support differs. Confirm the deployed Node version before choosing this path.
Proxy URLs must not contain credentials in scripts, repositories, logs, or unprotected environment variables. Use the platform's secret mechanism if authentication is required.
System changes
- Only changes environments, service definitions, CI settings, or npm proxy configuration if the operator intentionally applies an approved variant.
- Affected Node processes must be restarted to load new extra CA certificates.
Syntax explained
NODE_EXTRA_CA_CERTS={{caBundle}}- Extends Node's default trust with the reviewed PEM certificates when set before process startup.
NODE_USE_SYSTEM_CA=1- Requests the system CA store on Node versions that support it; verify version and platform behavior before adoption.
npm https-proxy- Routes npm HTTPS requests through an explicitly configured proxy. It is not a substitute for authenticating the proxy CA.
NODE_EXTRA_CA_CERTS=/etc/company/pki/node-ca-bundle.pem HTTPS proxy: not configured Node process restarted after trust change
Checkpoint:
Continue when
Stop when
If this step fails
npm succeeds but a lifecycle script still reports a certificate error
Likely causeThe child tool uses Node, Git, Java, Python, or its own CA configuration rather than npm cafile.
node -p process.versionnpm config get cafile
ResolutionIdentify the failing executable and configure its documented trust store; do not disable verification globally.
Security notes
- Scope NODE_EXTRA_CA_CERTS to the smallest service or CI environment that needs the private CA.
- Never set NODE_TLS_REJECT_UNAUTHORIZED=0; it disables certificate verification for Node TLS clients.
Alternatives
- For containers, install the approved CA into a versioned base image through the distribution trust mechanism and rebuild rather than mutating running containers.
Stop conditions
- Stop if the request uses an undocumented proxy, the Node version does not support the selected trust mode, or a child tool still reaches an unknown host.
verification
Verify registry TLS and package metadata
Start a fresh shell or CI process so Node reads the intended environment, then verify npm configuration, registry ping, and package metadata. Keep strict verification enabled throughout. This proves both the control plane endpoint and a real package lookup without running install scripts.
Why this step matters
A successful ping alone may not cover package metadata or tarball routing. The metadata lookup confirms the selected package and reveals the next host that an install would contact.
What to understand
Confirm that strict-ssl remains true and cafile resolves to the reviewed bundle.
Check that the tarball host is approved and uses HTTPS.
Run the same checks as the failing service or CI identity.
Do not consider cached output sufficient; use npm's normal online request path and record timestamps.
If a private registry requires authentication, use a least-privilege read token through the approved secret mechanism and never include it in evidence.
System changes
- npm can update local cache metadata and a debug log, but it does not alter project dependencies or run package scripts.
Syntax explained
npm ping- Checks the configured registry endpoint.
npm view ... version- Reads package metadata without installing it.
dist.tarball- Shows the artifact URL that a later installation would fetch.
Values stay on this page and are never sent or saved.
npm config get strict-ssl && npm config get cafile && npm ping --registry={{registryUrl}} && npm view {{packageName}} version dist.tarball --registry={{registryUrl}}true /etc/company/pki/node-ca-bundle.pem npm notice PING https://registry.npmjs.org/ npm notice PONG 178ms version = '5.0.0' dist.tarball = 'https://registry.npmjs.org/example/-/example-5.0.0.tgz'
Checkpoint:
Continue when
Stop when
If this step fails
Ping succeeds but metadata fails with the same TLS error
Likely causeThe package scope or metadata request uses a different registry, proxy, or certificate path.
npm config get @{{packageScope}}:registrynpm view {{packageName}} dist.tarball --json --registry={{registryUrl}}
ResolutionInspect the newly identified host and extend trust only after its owner and chain are verified.
Security notes
- Do not expose private package names, registry paths, or tokens in public evidence.
- Success is valid only with strict-ssl=true.
Alternatives
- Use a known organization-owned test package in private registries to avoid disclosing sensitive package metadata.
Stop conditions
- Stop if any returned URL is HTTP, unexpected, credential-bearing, or outside approved registry domains.
command
Run a bounded install canary without lifecycle scripts
After read-only verification passes, use npm's dry-run and disable lifecycle scripts to inspect the dependency transaction for a representative package. Review registry and tarball destinations before allowing a normal installation in production. This is a canary, not permission to ignore a changed lockfile or unreviewed dependency.
Why this step matters
The dry run exercises dependency resolution and artifact access while avoiding package lifecycle execution and intended project mutation.
What to understand
Use an existing reviewed dependency and pinned version, not latest.
Run in a disposable checkout or controlled canary workspace if npm-version behavior around dry-run is uncertain.
Keep --ignore-scripts for the connectivity canary; lifecycle scripts are a separate supply-chain and execution review.
Compare registry and tarball hosts with the inventory from the earlier step.
A successful canary proves this path now validates; it does not prove every private scope or external Git dependency uses the same CA.
System changes
- npm may write cache entries and debug logs.
- The dry-run should not apply the planned dependency-tree changes, but use a disposable checkout and verify git status because npm behavior varies by version and project state.
Syntax explained
--dry-run- Reports the planned install without intentionally applying it.
--ignore-scripts- Prevents package lifecycle scripts from executing during the TLS canary.
--no-audit --no-fund- Avoids unrelated network requests that could confuse endpoint diagnosis.
{{packageName}}@{{packageVersion}}- Pins a reviewed package and version instead of resolving a moving latest tag.
Values stay on this page and are never sent or saved.
npm install --dry-run --ignore-scripts --no-audit --no-fund {{packageName}}@{{packageVersion}}add example 5.0.0 added 1 package in 612ms Run npm install without --dry-run only after reviewing the resolved package, version, registry, and scripts.
Checkpoint:
Continue when
Stop when
If this step fails
The canary fails on a Git or different tarball host
Likely causeThe dependency graph includes an endpoint outside the npm registry trust path.
git status --shortnpm view {{packageName}} dist.tarball --json --registry={{registryUrl}}
ResolutionReview that tool and endpoint separately; do not add unrelated roots to the npm bundle.
Security notes
- Do not remove --ignore-scripts until the package and its lifecycle scripts have been reviewed.
- Do not run a canary as root or with production secrets.
Alternatives
- In a CI system, run the canary in an ephemeral unprivileged job with a clean cache and no deployment credentials.
Stop conditions
- Stop if the dry run proposes an unexpected package, version, registry, Git source, or large dependency change.
- Stop if git status changes and the reason is not understood.
verification
Close the change with durable verification and ownership
Record the validated trust source, fingerprint, affected configuration scope, Node/npm versions, registry hosts, expiry or review date, and successful checks. Remove temporary diagnostics and unsafe legacy overrides. Add monitoring for certificate expiry and a canary that fails closed when TLS verification breaks.
Why this step matters
Certificate incidents recur when ownership, fingerprints, configuration scope, and renewal dates are not captured. Final evidence turns an emergency workaround into a maintainable trust decision.
What to understand
Remove any strict-ssl=false, NODE_TLS_REJECT_UNAUTHORIZED=0, HTTP registry, or temporary inline CA setting discovered during the incident.
Do not record registry tokens, proxy passwords, full internal URLs, or raw debug logs in the change record.
Assign ownership for CA renewal and for updating CI images, developer bootstrap, and container bases.
Test one scheduled registry metadata request with strict verification and alert before the private CA or intermediate expires.
Document how to rotate the CA with overlap so old and new chains can be validated during transition.
System changes
- None from the verification command. Follow-up configuration cleanup or monitoring changes require their own reviewed change.
Syntax explained
process.version- Records the Node release whose trust behavior was verified.
process.env.NODE_EXTRA_CA_CERTS||null- Shows whether this fresh process received an extra CA path without printing certificate content.
Values stay on this page and are never sent or saved.
npm config get strict-ssl && npm config get cafile && npm ping --registry={{registryUrl}} && node -e "console.log({node:process.version,extraCA:process.env.NODE_EXTRA_CA_CERTS||null})"true
/etc/company/pki/node-ca-bundle.pem
npm notice PONG 164ms
{ node: 'v24.6.0', extraCA: null }Checkpoint:
Continue when
Stop when
If this step fails
The error returns only in scheduled CI
Likely causeThe runner image, service identity, npmrc path, secret mount, or Node process environment differs from the interactive verification.
node --versionnpm --versionnpm config get userconfignpm config get cafile
ResolutionCompare the failing runner with the recorded successful context and deploy the CA through the runner's managed configuration.
Security notes
- Retain only sanitized evidence.
- Treat unexplained CA or registry changes as security events, not routine package-manager failures.
Alternatives
- Export a small verification script that prints only versions, registry hostname, strict-ssl, cafile path, and pass/fail status; keep it free of tokens and full configuration dumps.
Stop conditions
- Do not close the incident if verification depends on disabled TLS, a manually accepted leaf certificate, or an unowned CA bundle.
Finish line
Verification checklist
npm config get strict-sslThe command prints true.npm config get cafileThe command prints the approved absolute PEM bundle path, or null when the supported system CA mode is intentionally used.npm ping --registry={{registryUrl}}npm reports PONG or Ping success without UNABLE_TO_GET_ISSUER_CERT_LOCALLY or SELF_SIGNED_CERT_IN_CHAIN.npm view {{packageName}} version --registry={{registryUrl}}npm prints a package version and exits successfully without weakening TLS.openssl s_client -connect {{registryHost}}:443 -servername {{registryHost}} -verify_return_error </dev/nullThe certificate subject, issuer, hostname, and verification result match the approved registry or authorized interception service.Recovery guidance
Common problems and safe checks
npm error code UNABLE_TO_GET_ISSUER_CERT_LOCALLY
Likely causeThe peer omitted an intermediate, npm lacks the private root, the cafile path is wrong, or the process reached an intercepted or unexpected endpoint.
npm config get cafileopenssl s_client -showcerts -verify_return_error -connect {{registryHost}}:443 -servername {{registryHost}} </dev/null
ResolutionAuthenticate the intended chain, repair the server intermediate when appropriate, or configure the approved CA bundle while keeping strict-ssl=true.
npm error code SELF_SIGNED_CERT_IN_CHAIN
Likely causeA self-signed organization or private-registry root appears in the chain but is not configured as a trusted anchor, or an unexpected proxy substituted the certificate.
npm config get registrynpm config get strict-sslnpm config get cafile
ResolutionConfirm the issuer and fingerprint with the PKI owner, then configure the approved CA through cafile or managed system trust. Investigate any unexpected issuer.
npm ping works in a browser or terminal but fails in CI
Likely causeThe runner uses a different Node version, npmrc, service identity, container image, proxy path, or CA mount.
node --versionnpm --versionnpm config get userconfignpm config get cafile
ResolutionDeploy and verify the CA in the runner's own managed environment and restart the process.
npm config get cafile is correct but the error remains
Likely causeThe bundle is unreadable or malformed, another scope overrides it, the needed intermediate is absent, or the failing host differs from the registry.
openssl crl2pkcs7 -nocrl -certfile {{caBundle}} | openssl pkcs7 -print_certs -nooutnpm config get @{{packageScope}}:registry
ResolutionValidate the bundle, configuration precedence, file permissions, and actual failing hostname before changing trust.
npm ping succeeds but npm install fails
Likely causeA tarball CDN, private scope, Git dependency, or lifecycle script uses a different TLS client or host.
npm view {{packageName}} dist.tarball --json --registry={{registryUrl}}npm config get @{{packageScope}}:registry
ResolutionIdentify the exact executable and destination, then validate its documented trust path separately.
NODE_EXTRA_CA_CERTS has no effect
Likely causeThe process was already running, the file is malformed, Node runs with elevated setuid/capability behavior, or application code supplies its own ca option.
node -e "console.log(process.version, process.env.NODE_EXTRA_CA_CERTS)"openssl x509 -in {{caBundle}} -noout -subject -issuer
ResolutionStart a fresh supported Node process, validate the PEM file, and inspect application-specific TLS options.
The certificate works on one network but not another
Likely causeVPN, split DNS, IPv4/IPv6, transparent inspection, or explicit proxy policy changes the endpoint or issuer.
npm config get proxynpm config get https-proxyopenssl s_client -connect {{registryHost}}:443 -servername {{registryHost}} </dev/null
ResolutionCompare sanitized issuer and routing evidence from both paths and align the authorized network policy.
The CA bundle parses but is rejected as expired or not yet valid
Likely causeThe CA or intermediate is outside its validity window, or the system clock is wrong.
date -uopenssl crl2pkcs7 -nocrl -certfile {{caBundle}} | openssl pkcs7 -print_certs -text -noout
ResolutionCorrect verified time synchronization or obtain a renewed CA chain; never override date validation.
The peer certificate hostname is wrong
Likely causeDNS, proxy routing, virtual-host selection, SNI, or registry configuration points to the wrong service.
npm config get registryopenssl s_client -connect {{registryHost}}:443 -servername {{registryHost}} </dev/null
ResolutionRepair routing or endpoint configuration. Do not trust the wrong certificate.
A private registry requires a client certificate
Likely causeMutual TLS is enabled and registry-scoped client key/certificate configuration is missing or wrong.
npm config get registrynpm config get cafile
ResolutionUse the registry's documented scoped keyfile and certfile settings with protected files; do not confuse client identity with server CA trust.
A developer solved the error with strict-ssl=false
Likely causeCertificate validation was bypassed rather than repaired.
npm config get strict-sslnpm config get cafile
ResolutionRe-enable strict-ssl immediately, authenticate the intended CA, configure it correctly, and repeat every verification step.
A CA rotation breaks only long-running services
Likely causeThey loaded NODE_EXTRA_CA_CERTS or CA state at startup and were not restarted after the overlapping bundle changed.
node -e "console.log(process.version, process.env.NODE_EXTRA_CA_CERTS)"npm config get cafile
ResolutionDeploy an overlapping approved bundle, restart services through change control, and verify before removing the retired CA.
Reference
Frequently asked questions
What does npm UNABLE_TO_GET_ISSUER_CERT_LOCALLY mean?
Node/npm could not complete the registry certificate path to a locally trusted issuer. The cause may be a missing intermediate, private CA, authorized TLS interception, wrong cafile, or unexpected endpoint. Inspect the actual chain and authenticate the intended CA before changing trust.
What does npm SELF_SIGNED_CERT_IN_CHAIN mean?
The evaluated path includes a self-signed certificate that npm does not accept as a configured trust anchor. Confirm whether it is the approved organization or private-registry root. An unknown self-signed issuer is a security signal, not a certificate to trust automatically.
Should I set npm strict-ssl to false?
No. That disables certificate validation for npm registry connections. npm's documentation advises configuring a CA or CA file for an SSL-intercepting proxy instead of disabling protection.
Should I set NODE_TLS_REJECT_UNAUTHORIZED=0?
No. It disables TLS certificate verification for Node clients in that process environment and can expose credentials and packages to interception.
Can I export the certificate from my browser and trust it?
Not by itself. A browser may use a different path and trust store, and a certificate obtained through an untrusted connection proves only what that connection presented. Confirm the CA fingerprint with the PKI owner through an authenticated channel.
What is the difference between npm cafile and NODE_EXTRA_CA_CERTS?
npm cafile configures CA material for npm registry operations. NODE_EXTRA_CA_CERTS extends trust for a Node process when set before startup, so it has a broader effect and should be scoped carefully.
Why does npm work locally but fail in CI?
CI may use another Node/npm version, user, npmrc, container image, proxy, network route, or CA file. Collect evidence inside the failing job and deploy trust through the runner's managed configuration.
Why does npm ping pass but npm install still fail?
Install can contact scope-specific registries, tarball CDNs, Git hosts, or lifecycle tools that use different TLS clients. Inspect the failing hostname and tool instead of widening registry trust.
Should a private registry send the root certificate?
Servers normally send the leaf and required intermediates, while clients receive the root through a trusted administrative channel. Sending a root does not make it trusted.
How should Windows and macOS users install a corporate CA?
Prefer managed enterprise certificate deployment and a Node version that supports the organization's chosen system-CA behavior. Use npm cafile when an npm-specific protected PEM path is the reviewed approach.
How do I roll back the repair?
Restore the previous cafile, proxy, registry, and process environment in the same scopes you changed, keep strict-ssl=true, restart affected Node processes, and rerun certificate and npm verification. If the old state was insecure, do not restore it.
How often should this guide be reviewed?
Review it within 90 days and whenever Node or npm trust behavior, registry hosting, proxy policy, private CA certificates, CI images, or operating-system certificate deployment changes.
Recovery
Rollback
Restore only the npm and process trust settings changed during this procedure. Keep strict-ssl enabled; if the previous state disabled it, escalate instead of restoring the insecure setting.
- Stop new installs and builds that depend on the changed trust path.
- Restore the previous cafile, registry, proxy, and environment values from the sanitized change record. If cafile was previously unset, run npm config delete cafile --location=user.
- Remove NODE_EXTRA_CA_CERTS or NODE_OPTIONS only from the exact service, CI job, shell profile, or environment where this guide added it; restart affected processes because Node reads extra CAs at process start.
- Run npm config get strict-ssl and keep the result true.
- Repeat npm ping and the certificate-chain inspection. If the previous configuration still cannot validate the approved endpoint, leave package installation stopped and return the incident to the PKI or network owner.
Evidence