Error message
verify error:num=20:unable to get local issuer certificate
Also printed as
- Verify return code: 20 (unable to get local issuer certificate)
- error 20 at 0 depth lookup: unable to get local issuer certificate
s_client prints "verify error:num=20:…" during the handshake and "Verify return code: 20 (…)" in its summary; openssl verify prints "error 20 at <depth> depth lookup: …". By default s_client continues after verification errors; add -verify_return_error to make it fail.
OpenSSL CLI (openssl s_client / openssl verify) — X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
What it means
OpenSSL error 20 (X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY): while building the chain, OpenSSL reached a certificate whose issuer it could not find, neither among the certificates the server sent nor in the trusted store. The depth line printed just before it names the certificate whose issuer is missing. This is the same code behind curl's (60), Python's CERTIFICATE_VERIFY_FAILED and Node.js's UNABLE_TO_GET_ISSUER_CERT_LOCALLY.
Why it happens
The chain ends at a root that is not in the store
commonA private or company root (or a TLS-inspecting proxy that sends only its intermediate). The highest depth line names an intermediate whose issuer you do not have.
The server does not send its intermediate
commonOnly depth=0 appears, and its issuer is an intermediate CA. OpenSSL never downloads missing intermediates, so the server must send them.
No trust store was used for the test
occasionalopenssl verify with a lone certificate, or s_client in an image without ca-certificates, has no roots to finish the chain with, even for public sites.
How to fix it
- 1.
Read the depth lines
Depth 0 is the server's certificate, higher numbers are the intermediates it sent. The last depth line before the error is the certificate whose issuer is missing.
bash echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>&1 | grep -E '^depth|verify' # depth=1 CN=Example Corp Issuing CA <- the highest certificate received # verify error:num=20:... <- its issuer is not in the trust store - 2.
Test with the expected root
-CAfile with the correct root (and -verify_return_error to make failures fatal) shows whether that root completes the chain; "Verify return code: 0 (ok)" means it does.
bash # Verify against a specific root (exit code and "Verify return code: 0 (ok)" confirm it) echo | openssl s_client -connect api.example.com:443 -servername api.example.com \ -CAfile corp-root-ca.pem -verify_return_error 2>&1 | grep 'Verify return code' - 3.
Verify certificate files offline
Give openssl verify the root as -CAfile and the intermediates as -untrusted; a leaf alone cannot be verified.
bash # Offline: root as trust anchor, intermediate(s) as untrusted helpers openssl verify -CAfile corp-root-ca.pem -untrusted intermediate.pem server.pem - 4.
Install the root into the system store
When the chain legitimately ends at a company root, add it to the distribution's store; OpenSSL-based tools on the machine then trust it.
bash sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt sudo update-ca-certificates # openssl reads /etc/ssl/certs by default on Debian/Ubuntu
Related errors
verify error:num=21:unable to verify the first certificateOpenSSL CLI (openssl s_client)verify error:num=19:self-signed certificate in certificate chainOpenSSL CLI (openssl s_client / openssl verify)curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local…Python ssl module (OpenSSL)