Debugging TLS With openssl And Wireshark
メニューを表示するにはスワイプしてください
Something will break. A client will say "I can't connect to your site." A browser will throw a weird cert error. A CA will have an outage. SSL Labs will downgrade you from A to B and you'll have no idea what changed. Your debugging belt for TLS has three primary tools: openssl s_client, Wireshark, and SSL Labs.
This chapter is a practical tour of all three, with the specific commands and views that catch real problems.
Tool 1 — openssl s_client
The Swiss Army knife. Available on every Linux box, macOS, and (with winget) Windows. Connects to any TLS server and prints every detail of the handshake. The basic invocation:
openssl s_client -connect example.com:443
Hit Enter once it's connected to send an empty line, then Q to close. You'll see:
- The certificate chain — every cert from leaf to root, in order;
- The negotiated protocol —
TLSv1.3orTLSv1.2; - The negotiated cipher —
TLS_AES_256_GCM_SHA384,ECDHE-RSA-AES128-GCM-SHA256, etc; - Verify return code —
0 (ok)if everything validated, otherwise an error number; - OCSP response — if stapling is configured.
The Flags You'll Actually Use
-servername example.com— explicitly sets the SNI. Required for hosts that serve multiple sites on one IP. Add this every time;-showcerts— dumps every cert in the chain as full PEM. Pipe to a file when debugging chain issues;-tls1_3/-tls1_2— force a specific TLS version. Catches "server claims TLS 1.3 but actually doesn't";-cipher 'ECDHE-RSA-AES256-GCM-SHA384'— force a specific cipher suite (TLS 1.2);-ciphersuites 'TLS_AES_256_GCM_SHA384'— force a TLS 1.3 cipher suite;-verify_return_error— fail loudly on bad verification instead of soft-warning.
A practical example — checking a freshly-issued cert from inside a server:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer
Outputs the cert's notBefore and notAfter dates, subject, and issuer. Useful for "is this server serving the right cert?" sanity checks.
Common Errors And What They Mean
verify error:num=20:unable to get local issuer certificate— the chain is incomplete. The server sent the leaf but not the intermediate(s);verify error:num=21:unable to verify the first certificate— same problem, slightly different OpenSSL version's phrasing;verify error:num=10:certificate has expired— exactly what it says. Renew the cert;tlsv1 alert handshake failure— protocol or cipher mismatch. Server doesn't support what the client offered;SSL_connect:error in SSLv3 read server certificate B— usually a chain corruption issue;no peer certificate available— server didn't send a cert. Possibly a port misconfiguration (you're hitting an HTTP-only port);handshake failure (alert 40)— usually SNI mismatch. The hostname you asked for doesn't match any cert on the server. Add-servername.
If you remember one debugging command, make it openssl s_client -connect host:443 -servername host -showcerts. It catches 80% of real-world TLS errors.
Tool 2 — Wireshark
When you need to see what's actually on the wire — every byte, every packet — Wireshark is the answer. Start a capture, filter to tcp.port == 443, watch the handshake unfold.
With TLS 1.2, the entire handshake is visible in plaintext (until the ChangeCipherSpec). You can see the certificate chain in Wireshark's Packet Details pane and click through every field.
With TLS 1.3, everything from the ServerHello onward is encrypted. You can still inspect:
- ClientHello — version, cipher suites offered, SNI, extensions, supported_versions, key shares;
- ServerHello — chosen version, chosen cipher, server's key share;
- Then everything goes dark.
Decrypting Your Own TLS Traffic In Wireshark
To inspect the full payload in your own captures, browsers can write their session keys to a file via the SSLKEYLOGFILE environment variable. Tell Wireshark where to find that file in Preferences > Protocols > TLS > (Pre)-Master-Secret log filename, and it decrypts the traffic in real time.
# Linux/macOS
export SSLKEYLOGFILE=/tmp/keys.log
firefox
# Windows (PowerShell)
$env:SSLKEYLOGFILE = "C:\Users\you\keys.log"
.\firefox.exe
Now your captures show the actual HTTP requests and responses, even on TLS 1.3.
Crucial: never share an SSLKEYLOGFILE file. It's effectively a master key for every TLS connection your browser made during that session. Treat it like a password.
Tool 3 — SSL Labs
For public servers, Qualys SSL Labs (ssllabs.com/ssltest) is the remote audit gold standard. Free, no signup, point it at any domain, get a comprehensive report in 2-3 minutes:
- Letter grade from F to A+;
- Certificate — chain, expiry, key type, SAN coverage, CT logs;
- Protocol support — which TLS/SSL versions are enabled;
- Cipher list — what's offered, what clients negotiate;
- Key exchange — DH parameters, curves, signature algorithms;
- Handshake simulation — what 30+ different real-world clients would do connecting to your server;
- Vulnerability checks — Heartbleed, POODLE, BEAST, ROBOT, and more.
The report is opinionated and updated as new threats emerge. Run it on your production servers monthly. It catches drift — a cert renewed with a bad chain, a server upgraded with weaker defaults, an intermediate that fell out of CT logs.
For internal services not exposed to the public internet, use testssl.sh — an open-source command-line equivalent. Same checks, runs locally.
A Cheat Sheet For Common Production Issues
- "Site works in Chrome but breaks in Safari" — almost always an intermediate chain order issue. Run
openssl s_client -showcertsand verify the order is leaf → intermediate → (optional root). Re-deployfullchain.pemif needed; - "Random TLS timeouts from some clients" — middlebox (corporate firewall, ISP-level filter) dropping TLS 1.3. Test with
-tls1_2to confirm. Either tolerate the slow path or escalate to the network team; - "SSL Labs gave us a B" — usually one of: weak cipher still listed, no HSTS header, OCSP stapling broken, intermediate cert expired in CT logs. SSL Labs lists exactly which check failed;
- "Cert renewed but clients still see the old one" — Nginx wasn't reloaded.
systemctl reload nginx. Or, in containers, the container needs to restart to re-read the cert file; - "Browser says cert is for the wrong host" — SNI mismatch or wildcard issue. Check the cert's SAN list with
openssl x509 -noout -text < cert.pem | grep DNS.
フィードバックありがとうございます!
AIに質問する
AIに質問する
何でも質問するか、提案された質問の1つを試してチャットを始めてください