When running openssl s_client -host example.xyz -port 9093
, seeing both "bad certificate" alerts and "Verify return code: 0 (ok)" creates confusion. Let's dissect what's actually happening under the hood.
139810559764296:error:14094412:SSL routines:SSL3_READ_BYTES:sslv3 alert bad certificate:s3_pkt.c:1259:SSL alert number 42 39810559764296:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:184:
This indicates the server rejected your client certificate, not that the server's certificate is invalid. The key points:
- Alert 42 specifically means "bad certificate" in SSL/TLS
- This is a unilateral rejection from the server side
- The handshake failure occurs after certificate exchange
The "Verify return code: 0 (ok)" only means the client successfully verified the server's certificate. It doesn't indicate whether the server accepted your client certificate (if present).
Here are typical situations where this occurs:
1. Mutual TLS (mTLS) Configuration Issues
# Force client certificate requirement (server side) openssl s_server -accept 443 -cert server.pem -key server.key -verify 1
If your client doesn't provide a certificate or provides an invalid one, you'll see this error.
2. Certificate Chain Problems
Debug with:
openssl s_client -host example.xyz -port 9093 -showcerts -debug
3. Protocol Version Mismatch
Try forcing TLS version:
openssl s_client -host example.xyz -port 9093 -tls1_2
For proper debugging:
- Check server requirements: Does it expect client certificates?
- Verify certificate format: PEM vs DER, proper chain
- Test with minimal configuration: Start with basic connection then add complexity
Here's how to properly test mTLS:
# Server side openssl s_server -accept 9093 \ -cert server.crt -key server.key \ -CAfile ca.crt -verify 1 # Client side (with valid cert) openssl s_client -connect localhost:9093 \ -cert client.crt -key client.key \ -CAfile ca.crt
The complete session output should show successful verification on both sides.
When running openssl s_client -host example.xyz -port 9093
, you might encounter this confusing scenario:
139810559764296:error:14094412:SSL routines:SSL3_READ_BYTES:sslv3 alert bad certificate:s3_pkt.c:1259:SSL alert number 42
39810559764296:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:184:
...
Verify return code: 0 (ok)
The apparent contradiction occurs because:
- The remote server accepted your certificate (hence Verify return code 0)
- But the server's certificate failed validation on your client side
# Check certificate chain completeness
openssl s_client -showcerts -connect example.xyz:9093
# Verify against specific CA store
openssl verify -CAfile /path/to/cacert.pem server_cert.pem
Typical triggers include:
- Expired or not-yet-valid server certificate
- Missing intermediate certificates
- Hostname mismatch (CN/SAN doesn't match requested host)
- Revoked certificate (CRL/OCSP check failure)
For comprehensive debugging:
# Full connection test with debug
openssl s_client -connect example.xyz:9093 -debug -state -tlsextdebug
# Check certificate expiration
openssl x509 -in cert.pem -noout -dates
# Verify certificate chain
openssl verify -untrusted intermediate.pem -CAfile root.pem server.pem
This commonly occurs with certificates from commercial CAs:
# Without intermediate (fails)
openssl s_client -connect api.example.com:443 -CAfile root.crt
# With intermediate (works)
openssl s_client -connect api.example.com:443 -CAfile root.crt -cert_chain intermediate.crt
When implementing SSL in code, handle verification properly:
// Python example with certificate verification
import ssl
context = ssl.create_default_context()
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations('/etc/ssl/certs/ca-certificates.crt')
try:
with socket.create_connection(('example.xyz', 9093)) as sock:
with context.wrap_socket(sock, server_hostname='example.xyz') as ssock:
print(ssock.version())
except ssl.SSLError as e:
print(f"SSL Error: {e}")
If you control the server, verify:
- SSL protocols offered (avoid SSLv3)
- Certificate chain is properly configured
- Correct private key is loaded
# Apache SSL config example
SSLEngine on
SSLCertificateFile /path/to/cert.pem
SSLCertificateKeyFile /path/to/key.key
SSLCertificateChainFile /path/to/chain.pem
SSLProtocol all -SSLv2 -SSLv3