<!-- https://pkizerotohero.com/guides/add-root-ca-linux/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Add a private root CA to the Linux trust store on Debian, Ubuntu, RHEL, Fedora, SUSE and in containers, with real output, checks and clean removal.

Source: https://pkizerotohero.com/guides/add-root-ca-linux/ · Last reviewed: 2026-10-20

How-to guide · Trust a CA

# How to trust a private root CA on Linux

**In one sentence:**

Your Linux server, desktop or container image trusts your organisation's private root CA system-wide, and you know which programs keep their own list.

Task

[Trust a CA](https://pkizerotohero.com/guides/#task-trust)

Platforms

- Linux
- Docker

Who it is for

- [IT ops](https://pkizerotohero.com/start/#itops)
- [Developer](https://pkizerotohero.com/start/#developer)

Level

Practitioner · 6 steps

## Before you start

- The [root certificate](https://pkizerotohero.com/handbook/glossary/#root-certificate) of your [private CA](https://pkizerotohero.com/handbook/glossary/#private-ca) in PEM format (text starting with `-----BEGIN CERTIFICATE-----`). Never a private key.
- Its SHA-256 [fingerprint](https://pkizerotohero.com/handbook/glossary/#fingerprint), from a channel you trust.
- Root access (`sudo`).
- Your distribution family: `cat /etc/os-release` tells you.

Why it matters: every root in the system [trust store](https://pkizerotohero.com/handbook/glossary/#trust-store) can vouch for **any** name, for curl, git, Go programs, package managers and most other tools on the machine. Add only roots your organisation controls.

## Steps

### 1. Check the file

```bash
openssl x509 -in corp-root.crt -noout -subject -issuer -enddate -fingerprint -sha256 -ext basicConstraints
```

```text
subject=C=BE, O=Example Corp, CN=Example Corp Root CA
issuer=C=BE, O=Example Corp, CN=Example Corp Root CA
notAfter=Oct 17 01:43:50 2036 GMT
sha256 Fingerprint=BC:02:A4:F5:DC:68:B3:FD:CD:C4:78:03:0E:30:8C:B5:9D:6D:A0:84:5A:A0:B4:33:36:60:78:8E:96:C2:2E:01
X509v3 Basic Constraints: critical
    CA:TRUE
```

The fingerprint must match the published value, subject and issuer must be equal, and `CA:TRUE` must be set. If the file is DER (binary), convert it first: `openssl x509 -inform DER -in corp-root.cer -out corp-root.crt` ([DER and PEM](https://pkizerotohero.com/guides/convert-der-pem/)).

### 2. Debian, Ubuntu and Alpine

The file must be PEM, hold one certificate, and end in `.crt`, or [update-ca-certificates](https://manpages.debian.org/stable/ca-certificates/update-ca-certificates.8.en.html) ignores it:

```bash
sudo cp corp-root.crt /usr/local/share/ca-certificates/example-corp-root.crt
sudo update-ca-certificates
```

On Ubuntu 24.04 with Java installed, the output was:

```text
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
Processing triggers for ca-certificates-java (20240118) ...
Adding debian:example-corp-root.pem
done.
done.
```

`1 added` is what you want. The `rehash` warning is harmless. The `Adding debian:…` line is a hook that also put the root into the distribution’s Java `cacerts`. The result is the bundle `/etc/ssl/certs/ca-certificates.crt` plus a hashed directory, `/etc/ssl/certs`. Alpine uses the same folder and command once the `ca-certificates` package is installed.

### 3. RHEL, Fedora, CentOS Stream, Rocky, AlmaLinux and Amazon Linux

Here the folder accepts PEM or DER, and the anchors are trusted “for all purposes” ([update-ca-trust](https://gitlab.com/redhat/centos-stream/rpms/ca-certificates/-/blob/c10s/update-ca-trust.8.txt)):

```bash
sudo cp corp-root.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
```

This rebuilds `/etc/pki/ca-trust/extracted/`, including the PEM bundle behind `/etc/pki/tls/certs/ca-bundle.crt` and the Java keystore behind `/etc/pki/java/cacerts`. The p11-kit tool does the same in one step: `sudo trust anchor corp-root.crt` ([Red Hat](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/securing_networks/using-shared-system-certificates_securing-networks)).

### 4. SUSE and openSUSE

Local roots go in `/etc/pki/trust/anchors/` ([openSUSE ca-certificates](https://github.com/openSUSE/ca-certificates)):

```bash
sudo cp corp-root.crt /etc/pki/trust/anchors/
sudo update-ca-certificates
```

### 5. Container images

A container does not inherit the host’s trust store. Bake the root into the image at build time:

```dockerfile
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
    && rm -rf /var/lib/apt/lists/*
COPY corp-root.crt /usr/local/share/ca-certificates/example-corp-root.crt
RUN update-ca-certificates
```

For Alpine, install `ca-certificates` with `apk add --no-cache ca-certificates` and use the same two lines. For Red Hat UBI images, copy to `/etc/pki/ca-trust/source/anchors/` and run `update-ca-trust`. In Kubernetes you can also mount the bundle from a ConfigMap, but the image approach works everywhere.

### 6. Point the programs that keep their own list

| Program | Uses the system store? | What to do |
| --- | --- | --- |
| curl, wget, git, OpenSSL, Go, the distribution’s Python and `requests` | Yes | Nothing |
| Distribution OpenJDK | Yes, through the hook (Debian, Ubuntu) or `extracted/java` (RHEL family) | Nothing; other JDKs: see [Java trust stores](https://pkizerotohero.com/guides/add-root-ca-java/) |
| Node.js, npm | No | `node --use-system-ca`, or `NODE_EXTRA_CA_CERTS`: see [Python, Node.js and npm](https://pkizerotohero.com/guides/trust-ca-python-node/) |
| Python packages from pip (`requests` with certifi) | No | See [Python, Node.js and npm](https://pkizerotohero.com/guides/trust-ca-python-node/) |
| Chrome, Chromium | No, a per-user NSS database | Use NSS `certutil` (below) |
| Firefox | Its own store, except where the distribution links it to the system store (the Firefox packages of Fedora and RHEL do) | Import it under **Settings** > **Privacy & Security** > **View Certificates** > **Authorities**, or with an enterprise policy |

For Chrome on Linux, the [Chromium documentation](https://chromium.googlesource.com/chromium/src/+/main/docs/linux/cert_management.md) uses NSS `certutil` (package `libnss3-tools` on Debian and Ubuntu, `nss-tools` on Fedora). Recent versions use `~/.local/share/pki/nssdb`, or `~/.pki/nssdb` if that already exists:

```bash
DB="$HOME/.pki/nssdb"; [ -d "$DB" ] || DB="$HOME/.local/share/pki/nssdb"
certutil -d "sql:$DB" -A -t "C,," -n "Example Corp Root CA" -i corp-root.crt
certutil -d "sql:$DB" -L
```

`-t "C,,"` trusts the CA for TLS server certificates only. On a test database the listing showed `Example Corp Root CA C,,`.

## Check it worked

Before the change, curl on our Ubuntu test machine refused a server whose chain ends at the private root:

```bash
curl -sS https://intranet.example.com:18443/ -o /dev/null
```

```text
curl: (60) SSL certificate problem: unable to get local issuer certificate
```

After `update-ca-certificates`, the same command printed the status code `200`, and OpenSSL verified the whole chain:

```bash
openssl s_client -connect intranet.example.com:18443 -servername intranet.example.com -verify_hostname intranet.example.com </dev/null
```

```text
depth=2 C = BE, O = Example Corp, CN = Example Corp Root CA
verify return:1
depth=1 C = BE, O = Example Corp, CN = Example Corp Issuing CA 1
verify return:1
depth=0 CN = intranet.example.com
verify return:1
...
Verification: OK
Verify return code: 0 (ok)
```

On the RHEL family, `trust list --filter=ca-anchors | grep -A3 "Example Corp"` shows the new anchor.

## If something goes wrong

- **`0 added` on Debian or Ubuntu.** The file does not end in `.crt`, is DER instead of PEM, or is in the wrong folder.
- **The root is added, but [`unable to get local issuer certificate`](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/) or [verify error 20](https://pkizerotohero.com/errors/openssl-verify-error-20/) remains.** The chain probably ends at a different root, or the server leaves out its intermediate. Paste the server’s chain into the chain checker below, and [build the full chain file](https://pkizerotohero.com/guides/build-fullchain/) on the server.
- **curl, git and Go work, but Node.js or a pip-installed tool fails** with [`SELF_SIGNED_CERT_IN_CHAIN`](https://pkizerotohero.com/errors/node-self-signed-cert-in-chain/) or similar. That program reads its own bundle: see step 6.
- **It works on the host but not in the container**, often as [`x509: certificate signed by unknown authority`](https://pkizerotohero.com/errors/go-x509-unknown-authority/). Add the root to the image (step 5).
- **[`curl: (77) error setting certificate verify locations`](https://pkizerotohero.com/errors/curl-77-certificate-verify-locations/).** `SSL_CERT_FILE`, `CURL_CA_BUNDLE` or `--cacert` points to a file that does not exist; unset it, or point it at the system bundle.

*Interactive tool: Chain checker (runs in the browser at [/tools/chain/](https://pkizerotohero.com/tools/chain/))*

Warning

Remove roots you no longer need

A retired or test root keeps vouching for any name until you delete it. On Debian and Ubuntu, delete the file, then run `sudo update-ca-certificates --fresh`. In our test, a plain run left the old links in `/etc/ssl/certs`, and neither run removed the Java copy: delete it with `sudo keytool -delete -cacerts -alias debian:example-corp-root.pem`. On the RHEL family, delete the file and run `sudo update-ca-trust`, or use `sudo trust anchor --remove corp-root.crt`.

## Why this works

OpenSSL-based programs check a server’s [certificate chain](https://pkizerotohero.com/handbook/glossary/#certificate-chain) against a CA bundle file or a hashed directory, and accept it only if it ends at a certificate in there. `update-ca-certificates` and `update-ca-trust` rebuild those files from the distribution’s roots plus your local ones, so every program that reads them trusts the new root at the next connection. A root is trusted because it is in the store, not because it is self-signed ([Who do we trust?](https://pkizerotohero.com/handbook/trust-stores/)). The checks themselves are in [Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/), and designing a private CA that is safe to trust is in [A CA of your own](https://pkizerotohero.com/handbook/private-ca/).

## Go deeper

- [update-ca-certificates(8)](https://manpages.debian.org/stable/ca-certificates/update-ca-certificates.8.en.html): the Debian tool and its rules for local certificates.
- [Red Hat: using shared system certificates](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/securing_networks/using-shared-system-certificates_securing-networks): `update-ca-trust` and `trust anchor`.
- [update-ca-trust(8)](https://gitlab.com/redhat/centos-stream/rpms/ca-certificates/-/blob/c10s/update-ca-trust.8.txt): the source folders and every extracted file.
- [Chromium: certificate management on Linux](https://chromium.googlesource.com/chromium/src/+/main/docs/linux/cert_management.md): the NSS database Chrome uses.
- [curl: SSL certificate verification](https://curl.se/docs/sslcerts.html): how curl finds its CA bundle.

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/guides/add-root-ca-linux/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

### Learn why it works

- [Chapter 12: Who do we trust? CAs, trust stores and self-signed certificates](https://pkizerotohero.com/handbook/trust-stores/)
- [Chapter 22: A CA of your own: private two-tier PKI](https://pkizerotohero.com/handbook/private-ca/)
- [Chapter 13: Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)

### Error pages

- [SSL certificate problem: unable to get local issuer certificate](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/)
- [verify error:num=20:unable to get local issuer certificate](https://pkizerotohero.com/errors/openssl-verify-error-20/)
- [x509: certificate signed by unknown authority](https://pkizerotohero.com/errors/go-x509-unknown-authority/)
- [curl: (77) error setting certificate verify locations](https://pkizerotohero.com/errors/curl-77-certificate-verify-locations/)
- [SELF\_SIGNED\_CERT\_IN\_CHAIN](https://pkizerotohero.com/errors/node-self-signed-cert-in-chain/)

### Tools

- [Certificate decoder](https://pkizerotohero.com/tools/decoder/): Paste a PEM certificate and read every field in plain words.
- [Chain checker](https://pkizerotohero.com/tools/chain/): Paste a chain and see which link is missing, expired or out of order.

### Topics

- [Chains and trust stores](https://pkizerotohero.com/topics/chains-and-trust/)
- [Private PKI](https://pkizerotohero.com/topics/private-pki/)

## More guides: Trust a CA

- [How to work behind a TLS-inspecting corporate proxy, safely](https://pkizerotohero.com/guides/corporate-proxy-certificates/)
- [How to trust a private root CA on Windows](https://pkizerotohero.com/guides/add-root-ca-windows/)
- [How to trust a private root CA on macOS](https://pkizerotohero.com/guides/add-root-ca-macos/)
- [How to add a CA to a Java trust store (cacerts)](https://pkizerotohero.com/guides/add-root-ca-java/)
- [How to make Python, Node.js and npm trust a private CA](https://pkizerotohero.com/guides/trust-ca-python-node/)
