• 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

See How to set up mutual TLS.

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

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

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

In the gameLevel 23: Both sides show IDGuild Hall, Keystone. In development

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9