
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
When a production service returns a vague "SSL handshake failed" or browsers reject your certificate, you need to debug TLS issues with openssl s_client before guessing at configuration changes. This command-line tool exposes the exact negotiation step where connections break, from SNI mismatches to expired intermediates. Whether you are troubleshooting an Nginx reverse proxy, a Kubernetes ingress, or a legacy Java backend, this guide provides the precise flags and interpretation patterns that work on current OpenSSL 3.x releases.
openssl s_client -connect host:443 -servername host -showcerts. Inspect the verification depth, cipher suite negotiated, and certificate chain order. Use protocol-specific flags like -tls1_3 or -starttls smtp to isolate handshake failures caused by version mismatches or missing SNI headers.How do you debug TLS issues with openssl s_client for basic connectivity?
The most common failure mode is not a broken certificate but a misconfigured network path or missing Server Name Indication (SNI). When you install SSL certificates on Ubuntu or configure cloud load balancers, the server often hosts multiple domains on one IP. Without SNI, the server presents a default certificate that fails validation for your specific domain.
Always include the -servername flag. Omitting it is the single most frequent mistake engineers make when testing virtual hosts. The following command establishes a baseline connection while forcing the correct SNI header:
openssl s_client -connect api.example.com:443 \
-servername api.example.com \
-brief The -brief flag suppresses the verbose certificate dump and shows only the handshake result and negotiated parameters. If this succeeds but your application still fails, the problem lies in the application's TLS stack, not the network. If it fails with handshake failure, proceed to protocol isolation below.
Verifying non-standard ports and STARTTLS
TLS is not limited to port 443. Database servers, mail relays, and internal microservices often use opportunistic encryption. For these, you must signal the protocol upgrade explicitly:
- SMTP:
openssl s_client -connect mail.example.com:587 -starttls smtp -servername mail.example.com - PostgreSQL:
openssl s_client -connect db.internal:5432 -starttls postgres - IMAP:
openssl s_client -connect mail.example.com:143 -starttls imap
Without -starttls, the server expects plaintext and will either reset the connection or return garbage that OpenSSL interprets as a protocol error. This distinction matters when you manage PostgreSQL administration and enforce encrypted replication channels.
How do you verify certificate chains and expiration with openssl s_client?
A valid leaf certificate means nothing if the intermediate chain is incomplete. Browsers maintain their own intermediate stores, but servers must send the full chain. Missing intermediates cause intermittent failures across different clients and operating systems. When you debug TLS issues with openssl s_client, always inspect the chain depth.
openssl s_client -connect api.example.com:443 \
-servername api.example.com \
-showcerts < /dev/null 2>&1 | \
openssl x509 -noout -issuer -subject -dates This pipeline extracts just the metadata you need. Look for three things:
- Issuer matches subject of next cert: Each certificate's issuer field must exactly match the subject field of the certificate above it in the chain output.
- No self-signed intermediates: Only the root CA should be self-signed. An intermediate marked as self-signed indicates a misconfigured bundle.
- Dates are valid: Check both
notBeforeandnotAfter. Clock skew between servers can cause valid certificates to appear expired.
If the chain is broken, OpenSSL reports Verify return code: 21 (unable to verify the first certificate). Error code 20 means the issuer certificate is missing entirely. Error code 10 indicates the certificate has expired. These numeric codes are stable across versions and more reliable than parsing human-readable messages.
Testing against custom trust stores
In enterprise environments or when working with private CAs, the system trust store won't recognize your certificates. Point OpenSSL at your specific CA bundle:
openssl s_client -connect internal.corp:443 \
-servername internal.corp \
-CAfile /etc/ssl/certs/corp-root-ca.pem \
-verify_return_error The -verify_return_error flag is critical. Without it, OpenSSL completes the handshake even when verification fails, printing the error but returning exit code 0. With this flag, verification failures produce a non-zero exit code, enabling automated checks in CI pipelines and monitoring scripts. This pattern aligns with practices described in Ubuntu security hardening where explicit trust boundaries prevent silent failures.
How do you isolate protocol and cipher mismatches when debugging TLS?
Modern infrastructure often supports multiple TLS versions simultaneously. Legacy clients may require TLS 1.2 while security policies mandate TLS 1.3. Handshake failures frequently occur when the client and server share no common cipher suite or protocol version. You must test each combination independently.
Force specific protocol versions to identify compatibility gaps:
# Test TLS 1.3 support
openssl s_client -connect api.example.com:443 \
-servername api.example.com -tls1_3 -brief
# Test TLS 1.2 fallback
openssl s_client -connect api.example.com:443 \
-servername api.example.com -tls1_2 -brief
# Disable specific weak ciphers
openssl s_client -connect api.example.com:443 \
-servername api.example.com \
-cipher 'HIGH:!aNULL:!MD5:!RC4' -brief If TLS 1.3 fails but TLS 1.2 succeeds, the server likely lacks TLS 1.3 support or has a misconfigured cipher preference. In 2026, any public-facing service should support TLS 1.3. If both fail, check firewall rules and middleboxes. Some corporate proxies terminate and re-encrypt TLS, stripping modern extensions. The -msg flag reveals every handshake message exchanged, helping identify where the negotiation stalls.
| Flag | Purpose | When to Use |
|---|---|---|
-tls1_3 | Force TLS 1.3 only | Verify modern protocol support |
-tls1_2 | Force TLS 1.2 only | Test legacy client compatibility |
-cipher | Restrict cipher suites | Validate policy compliance |
-curves | Specify EC groups | Debug ECDHE key exchange failures |
-sigalgs | Limit signature algorithms | Troubleshoot RSA vs ECDSA issues |
-msg | Show handshake messages | Deep-dive protocol analysis |
How do you automate TLS validation in monitoring and CI pipelines?
Manual debugging solves immediate incidents, but preventing recurrence requires automation. Integrate openssl s_client into health checks and deployment gates. The key is using exit codes reliably rather than parsing text output.
#!/bin/bash
# tls-check.sh — Exit 0 only if chain validates and protocol is TLS 1.3+
HOST="${1:-api.example.com}"
PORT="${2:-443}"
RESULT=$(openssl s_client -connect "$HOST:$PORT" \
-servername "$HOST" \
-verify_return_error \
-tls1_3 \
-brief < /dev/null 2>&1)
if echo "$RESULT" | grep -q "Verification: OK"; then
echo "PASS: $HOST:$PORT TLS 1.3 verified"
exit 0
else
echo "FAIL: $HOST:$PORT"
echo "$RESULT"
exit 1
fi Run this script from your monitoring stack. Tools like Prometheus blackbox exporter wrap similar logic, but direct OpenSSL calls give you full control over verification parameters. For CI pipelines, add this as a post-deploy gate after certificate renewal. This catches configuration drift before users encounter errors.
Extracting structured data for dashboards
For ongoing observability, extract certificate expiry dates programmatically:
openssl s_client -connect api.example.com:443 \
-servername api.example.com < /dev/null 2>/dev/null | \
openssl x509 -noout -enddate | \
cut -d= -f2 Parse this output into metrics. Alert when expiry is within 30 days. Combine with the four golden signals framework to correlate TLS failures with saturation and error rates. Certificate expiry is a leading indicator; catching it early prevents midnight pages.
Debug TLS Issues with openssl s_client: Next Steps
Mastering openssl s_client transforms TLS troubleshooting from guesswork into a systematic diagnostic process. Start with SNI-aware basic connectivity tests, validate complete certificate chains against explicit trust stores, isolate protocol mismatches with version-specific flags, and embed these checks into your monitoring and CI workflows. The commands in this guide work on OpenSSL 3.x and remain compatible with current Linux distributions and container images in 2026.
If your team struggles with recurring certificate incidents or needs to establish audit-ready TLS governance across multi-cloud environments, reach out to discuss your infrastructure. Proper TLS hygiene is foundational to security posture and compliance readiness.