Error message
SSL routines:ssl3_read_bytes:ssl/tls alert bad certificate
Also printed as
- SSL routines:ssl3_read_bytes:sslv3 alert bad certificate
- error:0A000412:SSL routines::ssl/tls alert bad certificate
OpenSSL 3.3 and later print "ssl/tls alert bad certificate", earlier versions "sslv3 alert bad certificate" (code 0A000412; 1.1.1 used 14094412), followed by "SSL alert number 42". OpenSSL clients themselves send unknown_ca (48) or certificate_expired (45) for the same rejections.
OpenSSL — TLS alert 42 (bad_certificate) received from the peer
What it means
The peer sent TLS alert 42 (bad_certificate): it rejected the certificate this side presented. Go's crypto/tls sends this alert for any server certificate it fails to verify (unknown authority, expired, wrong name), so OpenSSL-based servers (nginx, HAProxy, Postfix, s_server) log it whenever a Go client rejects their certificate. In mutual TLS, a server sends it when it rejects the client's certificate.
Why it happens
A Go client rejected the server's certificate
commonGo programs (Kubernetes components, Terraform, Prometheus, Docker, many CLIs) send bad_certificate when verification fails. The Go side shows the real reason, for example "x509: certificate signed by unknown authority".
Mutual TLS: the server rejected the client certificate
commonThe client certificate is expired, not issued by an accepted CA, or otherwise unacceptable to the server.
The certificate's key usage does not allow its role
occasionalA certificate whose extendedKeyUsage lacks serverAuth (or clientAuth for client certificates) is rejected by strict verifiers such as Go.
How to fix it
- 1.
Find the rejecting client and the reason
The log line names the client address; on that client, the error message gives the real cause. Verifying the chain from outside often shows it directly.
bash # Server log: which client rejected the certificate, and does the chain verify from outside? openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \ | grep -E 'Verify return code' - 2.
Go clients: trust the CA or fix the chain
Serve a complete chain from a trusted CA, or add the private CA to the Go client's RootCAs; never use InsecureSkipVerify as the fix.
go // Go clients: trust the server's CA explicitly (keeps verification on) pool, _ := x509.SystemCertPool() caPEM, _ := os.ReadFile("internal-ca.pem") pool.AppendCertsFromPEM(caPEM) client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: pool}}} - 3.
Mutual TLS: check the client certificate
Validity dates, issuer and key usage of the client certificate must match what the server expects.
bash # Mutual TLS: check the client certificate the server rejected openssl x509 -in client.pem -noout -issuer -dates -ext extendedKeyUsage,keyUsage