# Rust "invalid peer certificate: UnknownIssuer": fixes

> rustls could not chain the server certificate to a root in its root store. Choose the right root source or add the missing root.

Source: https://ssl.sh/errors/rust-invalid-peer-certificate-unknown-issuer/
Last updated: 2026-11-10

**`invalid peer certificate: UnknownIssuer` means rustls could not connect the server's certificate chain to any root in the root store it was given.** Browsers may accept the same certificate. Your program trusts a compiled-in Mozilla list or an empty container bundle, while the chain ends at a private CA or lacks its intermediate. Fix the root source.

## What the error means

rustls found no root in its store that issued the server's chain. rustls returns `Error::InvalidCertificate(CertificateError::UnknownIssuer)`, which displays as `invalid peer certificate: UnknownIssuer` and appears in Debug output as `InvalidCertificate(UnknownIssuer)`. The rustls documentation describes the variant as a chain that is not issued by a known root certificate. In reqwest, hyper or tonic, it is the innermost source of the connection error:

```text
error sending request for url (https://example.com/): ...: invalid peer certificate: UnknownIssuer
```

(reqwest's own message only names the URL; print the error with its `source()` chain, for example with anyhow's `{:#}`, to see the rustls part.)

Which roots count as "known" depends on how the crates were configured:

- **webpki-roots:** Mozilla's public roots compiled into the binary; they change only when you update the crate.
- **rustls-native-certs:** loads the operating system store at runtime; on Unix it honours `SSL_CERT_FILE` and `SSL_CERT_DIR`.
- **rustls-platform-verifier:** delegates to the OS verifier on Windows, macOS and Android, and on Linux uses the system CA bundle with webpki.

reqwest 0.12's `rustls-tls` feature selects webpki-roots. reqwest 0.13 made rustls the default backend and uses rustls-platform-verifier by default, so upgrading changes which roots you trust.

## Causes, most common first

### 1. A corporate or private CA that the root store does not contain

TLS-inspecting proxies and internal services issue certificates from roots that are not in Mozilla's list. With webpki-roots they can never be trusted; with native roots they must be installed on the machine.

- **Confirm:** `openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -issuer` shows your company or a security vendor. See [corporate SSL inspection](https://ssl.sh/errors/corporate-ssl-inspection/).
- **Fix:** use native roots or the platform verifier and install the root in the OS, or add it in code.

### 2. The server does not send its intermediate certificate

rustls verifies only the certificates the server sends plus its roots; it does not fetch missing intermediates.

- **Confirm:** `openssl s_client -showcerts` returns only one certificate, or the [chain builder](https://ssl.sh/tools/chain-builder/) reports a missing intermediate.
- **Fix:** configure the full chain on the server ([missing intermediate certificate](https://ssl.sh/errors/missing-intermediate-certificate/)).

### 3. The container has no CA bundle

`scratch` images, and slim images without the ca-certificates package, lack `/etc/ssl/certs/ca-certificates.crt`. With native roots, rustls then has nothing to trust and every site fails.

- **Confirm:** `ls -l /etc/ssl/certs/` inside the container.
- **Fix:** install `ca-certificates` in the build stage and copy the bundle into the final image.

### 4. A self-signed development certificate

A self-signed server certificate is its own issuer and is unknown unless you add it as a root. Generate local certificates from a local CA instead, and trust that CA.

### 5. An outdated webpki-roots release

A public root added to Mozilla's program after your crate version was published is unknown. `cargo update -p webpki-roots` refreshes it.

## Fix commands by platform

Pick a root source that contains the server's root: the OS store for corporate and private CAs, or compiled-in roots plus the added private root.

**Check which root source you build with:**

```bash
cargo tree -i webpki-roots
cargo tree -e features -i reqwest | head -20
```

**reqwest 0.12, use the OS store instead of compiled-in roots (Cargo.toml):**

```toml
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots"] }
```

**reqwest 0.13, add a private root while keeping the platform roots:**

```rust
let pem = std::fs::read("/etc/ssl/certs/company-root.pem")?;
let client = reqwest::Client::builder()
    .tls_certs_merge(reqwest::Certificate::from_pem_bundle(&pem)?)
    .build()?;
```

On reqwest 0.12 use `.add_root_certificate(reqwest::Certificate::from_pem(&pem)?)`.

**Plain rustls with the OS store:**

```rust
let mut roots = rustls::RootCertStore::empty();
roots.add_parsable_certificates(rustls_native_certs::load_native_certs().certs);
let config = rustls::ClientConfig::builder().with_root_certificates(roots).with_no_client_auth();
```

**Linux hosts and images:**

```dockerfile
FROM debian:bookworm-slim AS certs
RUN apt-get update && apt-get install -y ca-certificates
COPY company-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates

FROM scratch
COPY your-binary /your-binary
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
```

**Windows and macOS:** with the platform verifier (reqwest 0.13 default), installing the root in the system store (Group Policy, `certlm.msc`, or Keychain) is enough.

## Don't disable certificate verification

Accepting invalid certificates hides the error and trusts any server. `danger_accept_invalid_certs(true)` (renamed `tls_danger_accept_invalid_certs` in reqwest 0.13) or a custom rustls `ServerCertVerifier` that always returns success makes the error disappear by trusting everything. reqwest's own documentation warns that *any* certificate for *any* site will then be trusted, including expired ones, and that it should only be a last resort. Anyone on the network path could then impersonate your API and capture tokens.

The safer alternatives are precise: trust the OS store with native roots or the platform verifier, add the one private root with `tls_certs_merge` / `add_root_certificate`, ship a CA bundle in containers, and fix incomplete chains on the server.

## Verify the fix

```bash
./your-binary https://example.com/
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt ./your-binary https://example.com/   # native roots: test a specific bundle
```

The request must succeed without `UnknownIssuer`, on the same image or OS you deploy to. Confirm the server's chain in the [SSL checker](https://ssl.sh/tools/ssl-checker/), and read [certificate chains explained](https://ssl.sh/learn/certificate-chains/) to see why a missing intermediate breaks rustls but not a browser.
