- OpenSSL
- Exact text checked with the real tool
tlsv1 alert unknown ca
Also seen in: curl, Python, nginx, Apache httpd, HAProxy, Envoy, Java (unknown_ca)
In one sentence
The other side of the connection rejected your certificate because it does not trust the CA that issued it: usually a client certificate in mutual TLS, sometimes your server's certificate.
Where you see it
In OpenSSL and everything built on it, on whichever side received the alert. As a client in mutual TLS:
$ openssl s_client -connect api.example.com:443 -cert client.pem -key client.key
...
2C7F0000:error:0A000418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:../openssl-3.2.3/ssl/record/rec_layer_s3.c:909:SSL alert number 48
curl shows it as curl: (56) OpenSSL SSL_read: OpenSSL/3.0.13: error:0A000418:SSL routines::tlsv1 alert unknown ca, errno 0 (code 35 over TLS 1.2), and Python as ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca. OpenSSL 1.1.1 numbers it error:14094418. On a server, the same line appears in the error log when a client rejected the server’s certificate.
The reason string is OpenSSL’s SSL_R_TLSV1_ALERT_UNKNOWN_CA (OpenSSL error list).
What it means
TLS peers can end a handshake with an alert. Alert 48, unknown_ca, means: “A valid certificate chain or partial chain was received, but the certificate was not accepted because the CA certificate could not be located or could not be matched with a known trust anchor” (RFC 9846, section 6.2). In “tlsv1 alert”, “tlsv1” is only OpenSSL’s historical prefix; it does not mean TLS 1.0 was used.
The key question is who sent it. The side that logs this message is the one whose certificate was rejected. If you are the client, the server did not accept your client certificate. If you are the server, a client did not accept yours.
Likely causes
Your client certificate comes from a CA the server does not trust
The most common case: a client certificate from the wrong CA, a test CA, or a new CA the server was never told about. Confirm which CAs the server accepts; it lists them in its certificate request:
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | grep -A3 'Acceptable client certificate CA names'
openssl x509 -in client.pem -noout -issuer
Acceptable client certificate CA names
O=Example Corp, CN=Example Client CA
Fix: get a client certificate from one of the listed CAs, or, if you run the server, add your client CA to the file it trusts for clients:
# nginx
ssl_client_certificate /etc/nginx/client-ca.pem;
ssl_verify_client on;
# Apache httpd
SSLCACertificateFile /etc/httpd/client-ca.pem
SSLVerifyClient require
You send your client certificate without its intermediate
If the server trusts only the root, it needs your intermediate to build the chain, and the client must send it. Confirm that the server’s CA file and your intermediate verify your certificate:
openssl verify -CAfile server-trusted-root.pem -untrusted client-intermediate.pem client.pem
Fix: put your certificate and the intermediate in one file and use it as the client certificate (cat client.pem client-intermediate.pem > client-chain.pem, then --cert client-chain.pem in curl or -cert_chain in s_client).
Clients reject your server’s certificate
If the message is in your server’s log, OpenSSL-based clients refused your certificate, usually because you do not send the intermediate or because they do not have your private root. Java clients send certificate_unknown instead, which OpenSSL logs as sslv3 alert certificate unknown (ssl/tls alert certificate unknown in 3.2 and later). Confirm what you send with openssl s_client -showcerts, and fix the chain: How to build the full chain file your server needs. For a private CA, install its root on the clients.
Check yours
Paste the certificate chain you send (your client chain, or your server’s from openssl s_client -showcerts). The checker shows whether the intermediate is there and which root the chain needs.
Learn why
- Both sides show ID: mutual TLS: how the server asks for and checks client certificates.
- Certificate chains and path validation: why the intermediate must be sent.
- Related: Java’s handshake_failure and curl: (35).
Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.