# unable to get local issuer certificate

The error "unable to get local issuer certificate" (OpenSSL verify error 20) means the verifier could not find the CA certificate that signed the certificate it is checking. There are only two causes: the server is not sending its intermediate CA certificates, or the root CA is missing from the local trust store. Run `openssl s_client -connect example.com:443 -showcerts` and count the certificates returned — if only one comes back, the server is misconfigured and you must append the intermediates to its certificate file; if the full chain comes back, the missing root is a local trust store problem.

Source: https://certguru.io/errors/unable-to-get-local-issuer-certificate/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

> **The exact message**
>
> ```
>   Verify return code: 20 (unable to get local issuer certificate)
>   ```
>   curl reports the same condition as `curl: (60) SSL certificate problem: unable to get local issuer certificate`.

## What the error actually means

Verification walks a chain. Your server certificate says "I was signed by *Example Intermediate CA*", so the verifier looks for a certificate belonging to *Example Intermediate CA*, then asks who signed *that*, and continues until it reaches a certificate in the local trust store.

This error is emitted at the moment that walk fails: the verifier holds a certificate, knows the name of its issuer, and cannot find a certificate for that issuer anywhere. It is a *missing link*, not an invalid signature and not an expired certificate.

There are exactly two places the link can be missing from, and the fix is completely different for each.

## Step 1 — find out which cause you have

*Ask the server what it sends*

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts /dev/null \
  | grep -c "BEGIN CERTIFICATE"
```

| What you see | Cause | Go to |
| --- | --- | --- |
| **1 certificate** returned, and the CA is a public one | The server is not sending its intermediates | [Cause A](#cause-a-the-server-is-not-sending-its-intermediates) |
| **2 or more** returned, still fails | The root is not in your local trust store | [Cause B](#cause-b-the-root-is-not-in-your-trust-store) |
| `Verify return code: 0 (ok)` | Chain is fine; your *application* is using a different trust store | [Cause B](#cause-b-the-root-is-not-in-your-trust-store) |

> **Do not omit -servername**
>
> Without `-servername`, OpenSSL does not send SNI, and a virtual-hosted server will answer with its
>   default certificate instead of the one you are debugging. That produces a different, misleading
>   error.

## Cause A — the server is not sending its intermediates

A TLS server is required to send its own certificate **and** every intermediate needed to reach a trusted root. Serving only the end-entity certificate is the single most common cause of this error.

The fix is to concatenate the certificates into one file, **leaf first, then each intermediate in order**, walking up toward the root:

*Build a correctly ordered chain file*

```bash
cat certificate.crt intermediate.crt > fullchain.crt
```

> **Order matters**
>
> The server certificate must come first. Some clients tolerate a jumbled chain, many do not, and
>   a wrongly ordered file often produces this same error on a subset of clients only — which is a
>   miserable thing to debug later.

Then point the server at the combined file:

#### nginx

nginx has no separate chain directive — `ssl_certificate` must point at the concatenated file.

    ```nginx
ssl_certificate     /etc/ssl/certs/fullchain.crt;
ssl_certificate_key /etc/ssl/private/private.key;
```

#### Apache

Apache 2.4.8 and later also read the chain from the certificate file.

    ```apache
SSLCertificateFile    /etc/ssl/certs/fullchain.crt
SSLCertificateKeyFile /etc/ssl/private/private.key
```

#### HAProxy

HAProxy expects the key appended to the same PEM file as the chain.

    ```bash
cat certificate.crt intermediate.crt private.key > /etc/haproxy/certs/site.pem
```

Reload the service, then confirm the chain is complete:

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

Expected output:

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

### Where do I get the intermediate?

Your CA publishes it, and the certificate itself tells you where. Read the *CA Issuers* URL out of the Authority Information Access extension:

```bash
openssl x509 -in certificate.crt -noout -text \
  | grep -A1 "Authority Information Access"
```

Expected output:

```
Authority Information Access:
    OCSP - URI:http://ocsp.example-ca.com
    CA Issuers - URI:http://crt.example-ca.com/intermediate.der
```

That file is usually DER-encoded, so [convert it to PEM](/convert/der-to-pem/) before concatenating it.

## Cause B — the root is not in your trust store

If the server returns a complete chain and verification still fails, the root CA at the top of that chain is not trusted by whatever is doing the verifying. This is normal for a private or corporate CA, and for containers built `FROM scratch` that ship no CA bundle at all.

You can confirm it by verifying against the root explicitly. If this succeeds, the certificate is fine and only trust is missing:

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

Expected output:

```
certificate.crt: OK
```

Then install the root:

#### Debian / Ubuntu

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

    The file must have a `.crt` extension and contain PEM, or `update-ca-certificates` silently skips it.

#### RHEL / Fedora

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

#### Alpine

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

    A bare `alpine` image has no trust store at all until you `apk add ca-certificates`.

#### macOS

```bash
sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain root-ca.crt
```

### Runtimes that ignore the system trust store

Several runtimes ship their own CA bundle and will keep failing after you have fixed the system store. Point each at the right bundle:

| Runtime | How to add the root |
| --- | --- |
| Node.js | `NODE_EXTRA_CA_CERTS=/path/to/root-ca.crt` |
| Python `requests` | `REQUESTS_CA_BUNDLE=/path/to/ca-bundle.crt` |
| Python `ssl` / `urllib` | `SSL_CERT_FILE=/path/to/ca-bundle.crt` |
| Git | `git config --global http.sslCAInfo /path/to/ca-bundle.crt` |
| Java | `keytool -importcert -trustcacerts -alias corp-root -file root-ca.crt -keystore $JAVA_HOME/lib/security/cacerts` |
| curl | `curl --cacert /path/to/root-ca.crt` |

## What not to do

> **Do not disable verification**
>
> `curl -k`, `verify=False`, `NODE_TLS_REJECT_UNAUTHORIZED=0` and `GIT_SSL_NO_VERIFY=true` all make
>   the error disappear by switching off the check that produced it. The connection is then
>   encrypted but unauthenticated, which means anything on the path can transparently intercept it.
>   Use these to confirm your diagnosis and nothing else.


## Frequently asked questions

### What does "verify error num=20" mean?

It is the numeric form of this same error. OpenSSL returns error code 20, `X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY`, when it cannot locate the issuer of a certificate it is trying to verify.

### Why does the site work in my browser but fail with curl?

Browsers quietly repair incomplete chains. They cache intermediates from previous visits and follow the Authority Information Access extension to fetch a missing intermediate over HTTP. OpenSSL and curl do neither, so a chain that is genuinely broken only fails outside the browser. The server is still misconfigured.

### Is -k or verify=False a valid fix?

No. Both disable certificate verification entirely, which removes the protection TLS exists to provide and makes the connection trivially interceptable. Use them only to confirm a diagnosis, never in anything that ships.

### How do I fix this in Python requests?

The underlying cause is the same. Point requests at a CA bundle that contains the missing issuer with `requests.get(url, verify='/path/to/ca-bundle.crt')`, or install `certifi` and make sure the corporate root is appended to its bundle.


## References

- [openssl-verify(1)](https://docs.openssl.org/3.5/man1/openssl-verify/) — verify error 20 and the flags that resolve it
- [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/curl-60-ssl-certificate-problem/
- https://certguru.io/errors/pkix-path-building-failed/
- https://certguru.io/errors/self-signed-certificate-in-chain/
- https://certguru.io/openssl/verify-certificate-chain/
- https://certguru.io/concepts/certificate-chain/
- https://certguru.io/concepts/root-vs-intermediate-ca/
- https://certguru.io/platforms/nginx/
- https://certguru.io/errors/certificate-verify-failed-python/
- https://certguru.io/errors/x509-signed-by-unknown-authority-go/

--
CertGuru — https://certguru.io
