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
- Platforms
- Linux
- Docker
- Level
- Practitioner · 6 steps
On this page
Before you start
- The root certificate of your private CA in PEM format (text starting with
-----BEGIN CERTIFICATE-----). Never a private key. - Its SHA-256 fingerprint, from a channel you trust.
- Root access (
sudo). - Your distribution family:
cat /etc/os-releasetells you.
Why it matters: every root in the system 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
openssl x509 -in corp-root.crt -noout -subject -issuer -enddate -fingerprint -sha256 -ext basicConstraints
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).
2. Debian, Ubuntu and Alpine
The file must be PEM, hold one certificate, and end in .crt, or update-ca-certificates ignores it:
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:
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):
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).
4. SUSE and openSUSE
Local roots go in /etc/pki/trust/anchors/ (openSUSE ca-certificates):
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:
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 |
| Node.js, npm | No | node --use-system-ca, or NODE_EXTRA_CA_CERTS: see Python, Node.js and npm |
Python packages from pip (requests with certifi) |
No | See Python, Node.js and npm |
| 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 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:
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:
curl -sS https://intranet.example.com:18443/ -o /dev/null
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:
openssl s_client -connect intranet.example.com:18443 -servername intranet.example.com -verify_hostname intranet.example.com </dev/null
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 addedon 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 certificateor 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 on the server. - curl, git and Go work, but Node.js or a pip-installed tool fails with
SELF_SIGNED_CERT_IN_CHAINor 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. Add the root to the image (step 5). curl: (77) error setting certificate verify locations.SSL_CERT_FILE,CURL_CA_BUNDLEor--cacertpoints to a file that does not exist; unset it, or point it at the system bundle.
Why this works
OpenSSL-based programs check a server’s 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?). The checks themselves are in Certificate chains and path validation, and designing a private CA that is safe to trust is in A CA of your own.
Go deeper
- update-ca-certificates(8): the Debian tool and its rules for local certificates.
- Red Hat: using shared system certificates:
update-ca-trustandtrust anchor. - update-ca-trust(8): the source folders and every extracted file.
- Chromium: certificate management on Linux: the NSS database Chrome uses.
- curl: SSL certificate verification: how curl finds its CA bundle.
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.