oven-sh/bun · error · CertError

SELF_SIGNED_CERT_IN_CHAIN

SELF_SIGNED_CERT_IN_CHAIN

Error message

SELF_SIGNED_CERT_IN_CHAIN

What it means

X509 verify result 19 (X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN): a self-signed certificate was found in the chain (typically the root, or a corporate intermediate) but it is not in the local trust store, so the chain cannot be anchored. Mapped via get_cert_error_from_no (src/http/lib.rs:1538) to CertError::SELF_SIGNED_CERT_IN_CHAIN.

Source

Thrown at src/http/error.rs:159

    #[error("CERT_HAS_EXPIRED")]
    CERT_HAS_EXPIRED,
    #[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")]

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Install the corporate/private root CA: export it and point Bun at it via NODE_EXTRA_CA_CERTS=/path/to/corp-root.pem, or pass fetch(url, { tls: { ca: corpRootPem } })
  2. Fix the server to not send the self-signed root in the chain (leaf + intermediates only) — clients that already trust the root then verify fine
  3. In containers, bake the company root into the image's CA store
  4. Only as a last resort: rejectUnauthorized: false, understanding it disables all peer verification

Example fix

# before
await fetch("https://internal.corp/api"); // SELF_SIGNED_CERT_IN_CHAIN

# after
NODE_EXTRA_CA_CERTS=/usr/local/share/corp-root.pem bun run app.ts
Defensive patterns

Strategy: try-catch

Validate before calling

// fail fast at boot if a corporate root is configured but unreadable
import { readFileSync, existsSync } from "node:fs";
const corpCa = process.env.NODE_EXTRA_CA_CERTS;
if (corpCa && !existsSync(corpCa)) throw new Error(`NODE_EXTRA_CA_CERTS points to missing file: ${corpCa}`);

Type guard

function isCertErrorCode(e: unknown, code = "SELF_SIGNED_CERT_IN_CHAIN"): e is Error & { code: string } {
  return e instanceof Error && (e as any).code === code;
}

Try / catch

try {
  await fetch("https://internal.corp/api");
} catch (e) {
  if (isCertErrorCode(e, "SELF_SIGNED_CERT_IN_CHAIN")) {
    // corporate proxy root not trusted — load it explicitly instead of disabling verification
    const res = await fetch("https://internal.corp/api", {
      tls: { ca: readFileSync("/usr/local/share/corp-root.pem", "utf8") },
    });
  } else throw e;
}

Prevention

When it happens

Trigger: TLS handshake where the server sends its full chain including the self-signed root (unnecessary but common), and that root is absent from Bun's CA bundle — typical behind corporate TLS-inspecting proxies (Zscaler, Netskope, Blue Coat) or with private CAs.

Common situations: Corporate laptops behind SSL-inspection proxies whose root CA isn't installed for Bun, containers missing the company root, private service meshes whose root isn't distributed, or servers configured to send root.pem in the chain.

Understand the failure class

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/02ea67ee55bf53f2. Report an issue: GitHub.