oven-sh/bun · error · CertError
UNABLE_TO_GET_ISSUER_CERT_LOCALLY
UNABLE_TO_GET_ISSUER_CERT_LOCALLY
Error message
UNABLE_TO_GET_ISSUER_CERT_LOCALLY
What it means
X509 verify result 20 (X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY): the issuer of a certificate was found in the chain but the lookup against the local trust store failed — the chain's anchor (root) is not among the CAs the client trusts. Mapped via get_cert_error_from_no (src/http/lib.rs:1539) to CertError::UNABLE_TO_GET_ISSUER_CERT_LOCALLY. Distinct from UNABLE_TO_GET_ISSUER_CERT (2): here the chain is complete but untrusted locally.
Source
Thrown at src/http/error.rs:161
#[error("CRL_NOT_YET_VALID")]
CRL_NOT_YET_VALID,
#[error("CRL_HAS_EXPIRED")]
CRL_HAS_EXPIRED,
#[error("ERROR_IN_CERT_NOT_BEFORE_FIELD")]
ERROR_IN_CERT_NOT_BEFORE_FIELD,
#[error("ERROR_IN_CERT_NOT_AFTER_FIELD")]
ERROR_IN_CERT_NOT_AFTER_FIELD,
#[error("ERROR_IN_CRL_LAST_UPDATE_FIELD")]
ERROR_IN_CRL_LAST_UPDATE_FIELD,
#[error("ERROR_IN_CRL_NEXT_UPDATE_FIELD")]
ERROR_IN_CRL_NEXT_UPDATE_FIELD,
#[error("OUT_OF_MEM")]
OUT_OF_MEM,
#[error("DEPTH_ZERO_SELF_SIGNED_CERT")]
DEPTH_ZERO_SELF_SIGNED_CERT,
#[error("SELF_SIGNED_CERT_IN_CHAIN")]
SELF_SIGNED_CERT_IN_CHAIN,
#[error("UNABLE_TO_GET_ISSUER_CERT_LOCALLY")]
UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
#[error("UNABLE_TO_VERIFY_LEAF_SIGNATURE")]
UNABLE_TO_VERIFY_LEAF_SIGNATURE,
#[error("CERT_CHAIN_TOO_LONG")]
CERT_CHAIN_TOO_LONG,
#[error("CERT_REVOKED")]
CERT_REVOKED,
#[error("INVALID_CA")]
INVALID_CA,
#[error("PATH_LENGTH_EXCEEDED")]
PATH_LENGTH_EXCEEDED,
#[error("INVALID_PURPOSE")]
INVALID_PURPOSE,
#[error("CERT_UNTRUSTED")]
CERT_UNTRUSTED,
#[error("CERT_REJECTED")]
CERT_REJECTED,
#[error("SUBJECT_ISSUER_MISMATCH")]View on GitHub (pinned to 8c5296ac45)
Solutions
- Trust the private CA in Bun: set NODE_EXTRA_CA_CERTS=/path/to/org-ca.pem or pass it per-request via fetch(url, { tls: { ca: orgCaPem } })
- In containers, install ca-certificates and append the org CA to the system store (update-ca-certificates)
- Or get the service a publicly-trusted cert (Let's Encrypt) so no custom trust is needed
- Avoid rejectUnauthorized: false here — trusting the real CA is cheap and keeps verification on
Example fix
// before
await fetch("https://internal.corp.svc/api"); // UNABLE_TO_GET_ISSUER_CERT_LOCALLY
// after
import { readFileSync } from "node:fs";
await fetch("https://internal.corp.svc/api", {
tls: { ca: readFileSync("./org-ca.pem", "utf8") },
}); Defensive patterns
Strategy: try-catch
Validate before calling
// startup check: private CA present before any internal fetch
import { readFileSync } from "node:fs";
const CA_PATH = process.env.ORG_CA_PATH ?? "./org-ca.pem";
export const orgCa = readFileSync(CA_PATH, "utf8"); // throws at boot if missing — fail fast
// then always: fetch(url, { tls: { ca: orgCa } }) Type guard
function isCertErrorCode(e: unknown, code = "UNABLE_TO_GET_ISSUER_CERT_LOCALLY"): e is Error & { code: string } {
return e instanceof Error && (e as any).code === code;
} Try / catch
try {
await fetch("https://internal.corp.svc/api");
} catch (e) {
if (isCertErrorCode(e, "UNABLE_TO_GET_ISSUER_CERT_LOCALLY")) {
// chain is complete but root not trusted locally — supply the org CA and retry once
const res = await fetch("https://internal.corp.svc/api", { tls: { ca: orgCa } });
} else throw e;
} Prevention
- Bake the org CA into Docker images (ca-certificates + update-ca-certificates, or NODE_EXTRA_CA_CERTS)
- Fail fast at startup if required CA files are missing rather than at first internal request
- Prefer trusting the real CA over rejectUnauthorized:false — verification stays intact
When it happens
Trigger: fetch/https/Bun.connect to a server whose certificate chains to a private CA that is not in Bun's default root store — corporate/internal PKIs, self-hosted service meshes, test CAs — with reject_unauthorized left at its default true.
Common situations: Corporate environments with a private root CA not installed system-wide, minimal Docker images (alpine/distroless) missing ca-certificates, custom Kubernetes cluster CAs, freshly built machines without the org's CA package.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- UNABLE_TO_GET_ISSUER_CERT
- UNABLE_TO_GET_CRL
- UNABLE_TO_DECRYPT_CERT_SIGNATURE
- UNABLE_TO_DECRYPT_CRL_SIGNATURE
- UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY
AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16).
Data as JSON: /api/errors/d81c22f693ebb6f0.
Report an issue: GitHub.