invalid peer certificate: UnknownIssuer started appearing in pnpm installs on 28 September 2026, and most of the teams hitting it have not changed a line of their network config. The error has three unrelated causes, one of them a pnpm regression that makes the standard corporate-CA advice the thing that breaks the build. This walks through the single command that tells you which one you hit, and the correct file for each fix.

The version detail that matters up front: the defect landed in pnpm 12.6.0, the error text that names it landed in 12.8.0, and pnpm 12.8.2 (30 September 2026) fixes it. If you searched this error before today, everything you found was written for the Node-era UNABLE_TO_GET_ISSUER_CERT_LOCALLY and tells you to set NODE_EXTRA_CA_CERTS. On a slim Linux image running pnpm 12.6.0 to 12.8.1, that setting is the trigger.

Prerequisites

  • pnpm 12.x, from Corepack, a global npm install or the standalone script. Run pnpm --version first and keep the number in front of you. Every branch below keys off it.
  • Shell access to the environment that actually fails: a Docker build layer, a GitHub Actions runner, a Kubernetes job. Your laptop will usually not reproduce this, because it has a populated system trust store.
  • openssl on the failing host (apt-get install -y openssl on Debian slim images) to read the chain the connection is really getting.
  • Read access to .npmrc, pnpm-workspace.yaml and package.json. pnpm 12 split network configuration across all three, and the split trips people up more than the certificate does. It is one of several relocations in the pnpm 12 Rust CLI upgrade.
  • The ability to change the packageManager field in package.json. A stale pin there is the most common reason a team cannot move to the fixed release, the same field behind ERR_PNPM_PACKAGE_MANAGER_REMOVE_MODULES_DIR.
  • Optional: your organisation's root CA in PEM form, if it terminates and re-signs TLS.

Step-by-step

1. Read the host in the error before touching any config

pnpm install 2>&1 | grep -iE 'UnknownIssuer|https?://'

pnpm 12.8.0 started attaching the cause to resolution failures (release notes, issue #9556), and the example the maintainers picked in those notes is not a registry request at all: a Node.js runtime download behind a proxy that re-signs TLS. Registry fetches and runtime downloads do not share certificate configuration in pnpm, so the hostname in that line decides which half of this article applies to you. If it says nodejs.org, go to step 6. If it names your registry, keep going.

2. Pin the version window

pnpm --version

The regression exists from 12.6.0 to 12.8.1 inclusive. The reporter of issue #16365 confirmed that 11.28.2 succeeds with an identical command and identical environment. Two releases in between changed the shape of the symptom without fixing anything: pnpm 12.7.0 made requests to a server whose certificate fails verification "fail at once instead of being retried for more than a minute" (12.7 release notes, issue #9134). On 12.6.x the same defect looks like a slow network, so teams open tickets with their proxy team and never see a certificate error.

pnpm versionWhat you see
11.28.2 and earlierInstall succeeds; NODE_EXTRA_CA_CERTS is additive
12.6.0 to 12.6.xFails after retrying for over a minute, reads as a timeout
12.7.0 to 12.7.xFails immediately, cause not printed
12.8.0 to 12.8.1Fails immediately, prints invalid peer certificate: UnknownIssuer
12.8.2+Fixed for the no-system-CA case

3. Run the one test that separates the bug from a real proxy

env -u NODE_EXTRA_CA_CERTS pnpm install --lockfile-only

Dropping the variable and watching the failure disappear means cause A, the pnpm bug. Still failing means your connection is genuinely being re-signed and you are on cause B.

The mechanism explains why this only bites inside containers. pnpm's Rust network layer builds a platform TLS client from the system trust store and falls back to a bundled Mozilla root set when that store is unusable. On an image such as node:24-slim there are no system CA certificates, but the single extra CA from NODE_EXTRA_CA_CERTS was enough material to construct a valid client. The fallback never ran, and the client went to the registry trusting exactly one authority. PR #16367 reorders the attempt: build without the extra roots first, and only on failure fall back to the bundled Mozilla roots with the extra roots added on top.

pnpm request fails:invalid peer certificate: UnknownIssuerURL in the errorCause C:runtime download pathUnset NODE_EXTRA_CA_CERTSand retryCause A:additive-trust bug,pnpm 12.6.0-12.8.1Cause B:proxy re-signs TLS,no CA configuredUpgrade to pnpm 12.8.2or install ca-certificatesPer-registry cafilein .npmrcnodeDownloadMirrorsin pnpm-workspace.yaml"nodejs.org""your registry""now succeeds""still fails"

4. Fix cause A: upgrade, or give the image a trust store

corepack use pnpm@12.8.2

The 12.8.2 changelog entry names the exact environment: installs failing with UnknownIssuer on Linux systems without CA certificates, such as node:24-slim, when NODE_EXTRA_CA_CERTS is set (release notes, issue #16365).

When the version is pinned somewhere you cannot move this week, usually a packageManager field baked into a shared base image, give the image the store it was missing instead:

RUN apt-get update \
 && apt-get install -y --no-install-recommends ca-certificates \
 && rm -rf /var/lib/apt/lists/*

With a populated system store the platform client builds from it, the fallback question never comes up, and your extra CA goes back to being additive.

5. Fix cause B: point pnpm at your corporate CA, in the right file

# .npmrc - registry-scoped settings still live here in pnpm 12
//registry.npmjs.org/:cafile=/etc/ssl/corp/root-ca.pem
//npm.internal.example.com/:cafile=/etc/ssl/corp/root-ca.pem

pnpm's npmrc reference documents <URL>:cafile as the path to a Certificate Authority file used when accessing that specific registry, alongside :ca for an inline certificate and :certfile/:keyfile for client certificates. All of them are per registry. A bare cafile= line applies to nothing and produces no warning.

The trap sits one file over. strictSsl is a pnpm-workspace.yaml setting in pnpm 12, and it is camelCase:

# pnpm-workspace.yaml
strictSsl: true
noProxy: "internal.example.com,.corp.local"

A strict-ssl=false line inherited from an npm-era .npmrc is inert here. Teams add it, see no change, and conclude the certificate cannot be the problem. Leave strictSsl at its documented default of true and fix the trust path properly. Turning verification off behind an inspecting proxy removes your only evidence that the proxy is the one you think it is.

6. Fix cause C: the Node.js runtime download is a separate path

If step 1 showed nodejs.org, no registry-scoped cafile key will reach the request. pnpm downloads a runtime when devEngines.runtime in package.json asks for one; useNodeVersion and executionEnv.nodeVersion were removed in pnpm 11. Route the download through a mirror you already trust:

# pnpm-workspace.yaml
nodeDownloadMirrors:
  release: https://npm.internal.example.com/mirrors/node/

nodeDownloadMirrors replaced the old node-mirror .npmrc setting. To keep the public URL, get onto 12.8.2 first, where NODE_EXTRA_CA_CERTS extends the trust store on this path too.

Verify it works

Reproduce both sides in one image, with your own CA file:

docker run --rm -v "$PWD/corp-ca.pem:/tmp/ca.pem:ro" \
  -e NODE_EXTRA_CA_CERTS=/tmp/ca.pem node:24-slim \
  sh -c 'npm i -g pnpm@12.8.1 >/dev/null 2>&1 && pnpm view pnpm version'

Expect invalid peer certificate: UnknownIssuer. Change 12.8.1 to 12.8.2 and expect a bare version string.

Notice what that command proves along the way: npm i -g pnpm succeeds in the same container, over the same TLS, seconds before pnpm fails. Node consults its compiled-in Mozilla bundle unconditionally, while pnpm's Rust client asks the platform first. Two package managers, one image, opposite outcomes. "But npm works" is therefore not evidence against a certificate problem, and I have watched that argument send a team down the wrong path for most of an afternoon.

To confirm cause B independently of pnpm, read the issuer off the wire:

openssl s_client -connect registry.npmjs.org:443 \
  -servername registry.npmjs.org </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject

A public issuer means nobody is intercepting you. Your own company's CA in the issuer line confirms cause B whatever pnpm prints.

Common pitfalls

Putting cafile in pnpm-workspace.yaml. It is registry-scoped and belongs in .npmrc. pnpm does not warn; the key is simply never consulted.

Copying NODE_EXTRA_CA_CERTS into a Dockerfile as a blanket fix. On a slim base and pnpm 12.6.0 to 12.8.1 this converts a working build into a broken one. The commit usually looks unrelated because it lands in a base-image PR, not in the app repo.

Reading a pre-12.7.0 failure as a network flake. Retrying for over a minute before giving up looks exactly like a congested proxy. Check pnpm --version before you open the ticket.

Assuming the runtime download honours registry auth and CA settings. It does not, and has not for years. pnpm issue #3922 recorded the same split back in pnpm 6, where neither NODE_EXTRA_CA_CERTS nor cafile reached the nodejs.org index fetch.

Reaching for strictSsl: false. It suppresses verification on every request pnpm makes, tarballs included, and it stays in the repo long after the proxy is fixed.

FAQ

Do I need to run pnpm rebuild after upgrading to 12.8.2? No. The failure happened during resolution, before anything reached node_modules, so there is no half-installed state to repair.

Does the bug affect pnpm dlx as well as pnpm install? Yes. Every subcommand that fetches over HTTPS uses the same Rust client.

Is a packageManager pin a blocker? It is the usual reason teams are stuck. Corepack honours packageManager in package.json over anything installed globally, so bump that field rather than installing a newer pnpm on the runner.

Can I jump from 12.6.x straight to 12.8.2? Read the 12.7 and 12.8 notes first. 12.8.2 also sets childConcurrency to its documented default of 5, and 12.8.0 changed when build scripts re-run from cache, covered in the piece on pnpm build scripts not running because another project ran them.

Wrap-up

You have the URL test that names the cause, the version table that tells you whether a symptom is a hang or a hard failure, and the correct home for each setting: cafile per registry in .npmrc, strictSsl and nodeDownloadMirrors in pnpm-workspace.yaml, the runtime in devEngines.runtime.

Next, grep every base image and CI workflow you own for NODE_EXTRA_CA_CERTS and cross-check each hit against the packageManager field in the repos that consume it. Any pair where the variable is set and the pinned version falls between 12.6.0 and 12.8.1 is a build that breaks the next time its cache misses. That inventory is one search, and it is cheaper than finding out during a release. While you are in those Dockerfiles, the symlink behaviour behind pnpm deploy "Cannot find module" in Docker is worth a second look, since it lives in the same layer.