# Verify a certificate chain

To verify a certificate against a chain, run `openssl verify -CAfile root-ca.crt -untrusted intermediate.crt certificate.crt`. `-CAfile` names the trusted root, `-untrusted` supplies intermediates that are needed to build the path but are not themselves trusted, and the final argument is the certificate being checked. Output of `certificate.crt: OK` means a complete path to a trusted root was found. To verify against the system trust store instead of a specific root, omit `-CAfile`.

Source: https://certguru.io/openssl/verify-certificate-chain/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

## The command

*Verify against a specific root*

```bash
openssl verify -CAfile root-ca.crt -untrusted intermediate.crt certificate.crt
```

Expected output:

```
certificate.crt: OK
```

| Placeholder | What to put there |
| --- | --- |
| `-CAfile root-ca.crt` | The trust anchor. Certificates here are treated as trusted. |
| `-untrusted intermediate.crt` | Intermediates needed to build the path. Not treated as trusted. |
| `certificate.crt` | The certificate being verified. |

> **Do not put intermediates in -CAfile**
>
> It makes verification pass, but for the wrong reason — you have declared the intermediate a trust
>   anchor, so the check never reaches the root and would succeed even if the root were untrusted or
>   the intermediate revoked. Keep roots in `-CAfile` and intermediates in `-untrusted`.

## Against the system trust store

```bash
openssl verify -untrusted intermediate.crt certificate.crt
```

This is the check that matters for a publicly trusted certificate: it asks whether *your machine*
would accept it.

## Verifying what a server actually serves

Better than checking files, because it tests the deployed configuration including whether the
intermediates are being sent at all:

```bash
openssl s_client -connect example.com:443 -servername example.com /dev/null \
  | grep "Verify return code"
```

Expected output:

```
Verify return code: 0 (ok)
```

## Checking the hostname too

`openssl verify` validates the chain and says nothing about hostnames. To check both:

```bash
openssl verify -CAfile root-ca.crt -untrusted intermediate.crt \
  -verify_hostname example.com certificate.crt
```

> **Two independent checks**
>
> Chain validation and hostname matching are separate. A certificate can chain perfectly to a
>   trusted root and still be rejected because it does not cover the name you asked for — that is
>   `ERR_CERT_COMMON_NAME_INVALID` rather than an authority problem.

## Reading the errors

```bash
openssl verify certificate.crt
```

Expected output:

```
C = US, CN = example.com
error 20 at 0 depth lookup: unable to get local issuer certificate
error certificate.crt: verification failed
```

The `depth` number tells you where in the chain the problem is. Depth 0 is the certificate you
passed, depth 1 is its issuer, and so on.

| Error | Meaning | Fix |
| --- | --- | --- |
| 10 | Certificate has expired | [Check the dates](/openssl/check-certificate-expiry/) |
| 18 | Self-signed certificate | [Self-signed vs CA-signed](/concepts/self-signed-vs-ca-signed/) |
| 19 | Self-signed certificate in chain | [That error](/errors/self-signed-certificate-in-chain/) |
| 20 | Unable to get local issuer certificate | [That error](/errors/unable-to-get-local-issuer-certificate/) |
| 21 | Unable to verify the first certificate | Intermediates missing |
| 24 | Invalid CA certificate | Issuer lacks `CA:TRUE` in basic constraints |
| 26 | Unsupported certificate purpose | Extended key usage does not permit this use |

## Showing the whole chain that was built

Useful when verification succeeds but you want to know which path it took:

```bash
openssl verify -show_chain -CAfile root-ca.crt -untrusted intermediate.crt certificate.crt
```

Expected output:

```
certificate.crt: OK
Chain:
depth=0: CN = example.com (untrusted)
depth=1: C = US, O = Example CA, CN = Example Intermediate CA (untrusted)
depth=2: C = US, O = Example CA, CN = Example Root CA
```

Read it bottom-up. The last entry is the trust anchor; everything above it was validated against the
entry below.


## Frequently asked questions

### What is the difference between -CAfile and -untrusted?

`-CAfile` supplies certificates you are treating as trust anchors — normally just the root. `-untrusted` supplies intermediates that are needed to build the path but confer no trust themselves. Passing an intermediate to `-CAfile` makes verification succeed for the wrong reason, because you have declared the intermediate trusted.

### How do I verify against the system trust store?

Omit `-CAfile` entirely — `openssl verify -untrusted intermediate.crt certificate.crt` uses the default store. Add `-CApath /etc/ssl/certs` if your build does not pick it up automatically.

### Why does verify succeed but my browser still complains?

`openssl verify` checks the chain, not the hostname. A certificate can chain perfectly to a trusted root and still be for the wrong hostname. Check the SANs separately, or use `-verify_hostname example.com`.

### Can I verify the chain a live server sends?

Yes, and it is the better test because it checks what is actually deployed rather than what is on disk. Use `openssl s_client` and read its "Verify return code" line.


## References

- [openssl-verify(1)](https://docs.openssl.org/3.5/man1/openssl-verify/) — -CAfile, -untrusted and the numbered verify errors
- [RFC 4158: certification path building](https://www.rfc-editor.org/rfc/rfc4158) — why a verifier can fail to find a path that exists

## Related pages

- https://certguru.io/errors/unable-to-get-local-issuer-certificate/
- https://certguru.io/concepts/certificate-chain/
- https://certguru.io/openssl/get-certificate-from-server/
- https://certguru.io/concepts/root-vs-intermediate-ca/
- https://certguru.io/openssl/check-certificate-expiry/

--
CertGuru — https://certguru.io
