// Engineering Log
HTTP, HTTPS and TLS: Part 10 — Debugging TLS: curl, openssl s_client and troubleshooting certificate errors
Published on 2026-10-16
// Fast route
This article belongs to the topic Security and protection.
Most HTTPS problems boil down to a few reasons: incomplete certificate chain, expired certificate, incorrect name in the certificate, mismatch of versions or ciphers, or interference by intermediate equipment. The browser usually shows a generic “Connection is not secure”, and the exact reason is given by two tools: curl and openssl s_client. In this part — how to use them and how to read their output.
What curl -v shows
curl -sv -o /dev/null https://example.ru/ 2>&1 | grep '^\*'* Host example.ru:443 was resolved.
* Trying 192.0.2.10:443...
* Connected to example.ru (192.0.2.10) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: CN=example.ru
* start date: Sep 1 00:00:00 2026 GMT
* expire date: Nov 30 23:59:59 2026 GMT
* subjectAltName: host "example.ru" matched cert's "example.ru"
* issuer: C=US; O=Let's Encrypt; CN=R11
* SSL certificate verify ok.From the output you can see: which IP was connected to, which TLS version and cipher were negotiated, which protocol was selected via ALPN, to whom and by whom the certificate was issued, until when it is valid, and whether the name matched. The output format differs slightly depending on the TLS library curl was built with (OpenSSL, LibreSSL, GnuTLS, Schannel on Windows): curl --version shows it in the first line.
curl flags for TLS
curl --tlsv1.2 --tls-max 1.2 https://example.ru/ # TLS 1.2 only
curl --tlsv1.3 https://example.ru/ # not lower than TLS 1.3
curl --ciphers ECDHE-RSA-AES128-GCM-SHA256 --tls-max 1.2 https://example.ru/ # specific TLS 1.2 cipher
curl --cacert internal-ca.crt https://intranet.example.ru/ # trust your CA
curl --cert client.crt --key client.key https://admin.example.ru/ # client certificate (mTLS)
curl -k https://example.ru/ # don't verify certificate — diagnostic onlyThe --tlsv1.2 flag means “not below 1.2”, not “exactly 1.2”. To check whether the server supports specifically 1.2 you also need --tls-max 1.2.
-k (--insecure) is useful to separate a certificate problem from everything else: if everything works with -k, the issue is with the certificate. Do not leave -k in scripts.
openssl s_client
s_client is the TLS client from OpenSSL that performs the handshake and shows everything the server sent.
openssl s_client -connect example.ru:443 -servername example.ru </dev/null-servernamesets SNI. In OpenSSL 1.1.1 and newer SNI is taken from-connectautomatically if it contains a name and not an IP. If you connect by IP, specify-servernameexplicitly, otherwise you will get the default site’s certificate;</dev/nullcloses stdin so the command exits immediately after the handshake.
Main things in the output:
Certificate chain
0 s:CN=example.ru
i:C=US, O=Let's Encrypt, CN=R11
1 s:C=US, O=Let's Encrypt, CN=R11
i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
...
Verify return code: 0 (ok)- Certificate chain — what the server sent:
s— subject (to whom issued),i— issuer. Each nextsshould match the previousi. If the chain contains only line0, the server is not sending the intermediate certificate; - Verify return code: 0 (ok) — the chain is verified. Any other value is an error with an explanation.
Brief output and specific cases:
openssl s_client -connect example.ru:443 -brief </dev/null # only version, cipher, verification
openssl s_client -connect example.ru:443 -showcerts </dev/null # all certificates of the chain in PEM
openssl s_client -connect example.ru:443 -tls1_2 </dev/null # TLS 1.2 only
openssl s_client -connect example.ru:443 -alpn h2 </dev/null # check ALPN
openssl s_client -connect example.ru:443 -status </dev/null # OCSP response if the server attaches it
openssl s_client -connect mail.example.ru:587 -starttls smtp </dev/null # TLS in mail protocols-starttls works for smtp, imap, pop3, ftp, xmpp, postgres, mysql and other protocols where TLS is initiated by a command inside an open connection.
Certificate details
Validity period, names and issuer of the certificate on the server:
openssl s_client -connect example.ru:443 -servername example.ru </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltNamesubject=CN=example.ru
issuer=C=US, O=Let's Encrypt, CN=R11
notBefore=Sep 1 00:00:00 2026 GMT
notAfter=Nov 30 23:59:59 2026 GMT
X509v3 Subject Alternative Name:
DNS:example.ru, DNS:www.example.ruThe -ext flag is available in OpenSSL 1.1.1 and newer. For a certificate file on disk — openssl x509 -in cert.pem -noout -text.
Check whether the certificate will expire within the next 14 days (exit code 1 — will expire):
openssl s_client -connect example.ru:443 -servername example.ru </dev/null 2>/dev/null \
| openssl x509 -noout -checkend $((14*24*3600)) && echo ok || echo 'expiring'For continuous monitoring, use monitoring: Prometheus blackbox_exporter exposes the metric probe_ssl_earliest_cert_expiry — the expiry date of the earliest certificate in the chain, which is used to configure alerts.
Check that the key matches the certificate (hashes should match):
openssl x509 -in cert.pem -noout -pubkey | openssl sha256
openssl pkey -in privkey.pem -pubout | openssl sha256Verify a chain from files:
openssl verify -untrusted chain.pem cert.pemWhich versions and ciphers the server supports
Manually — by trying -tls1, -tls1_1, -tls1_2, -tls1_3 in s_client. More convenient are ready tools:
nmap --script ssl-enum-ciphers -p 443 example.ru
testssl.sh example.rutestssl.sh checks versions, ciphers, chain, HSTS, known vulnerabilities and works without installation, as a single script. For public sites SSL Labs does the same.
Decrypt traffic in Wireshark
How to capture traffic with tcpdump and work with Wireshark is described in the article Network Troubleshooting for Beginners: Part 5 — tcpdump, sngrep and Wireshark.
Sometimes you need to see the HTTP inside TLS: what exactly the application sends, which headers go out. For this the client writes session keys to a file and Wireshark decrypts the traffic with them.
export SSLKEYLOGFILE=$HOME/tls-keys.log
curl https://example.ru/ # curl built with OpenSSL, BoringSSL or GnuTLS
# or run Chrome/Firefox from the same shellIn Wireshark: Preferences → Protocols → TLS → (Pre)-Master-Secret log filename — specify the file. Traffic recorded during this time will appear decrypted, including HTTP/2.
The key file allows decrypting all recorded conversations — store it like a password and delete it after debugging. The system curl on macOS is built with LibreSSL and may not support SSLKEYLOGFILE; use curl from Homebrew.
Common errors and what they mean
unable to get local issuer certificate (curl, code 60; in s_client — verify error:num=20). The client cannot build a chain to a trusted root. Reasons:
- the server does not send the intermediate certificate — fix it on the server: use
fullchain.peminstead ofcert.pem; - the certificate was issued by an internal CA that the client doesn’t have — use
--cacertor install the root in the system; - the system has an outdated root store — update the
ca-certificatespackage.
certificate has expired (code 10). Check the dates. If the certificate on the server is fresh but the client sees an old one — the web server did not reload the certificate after renewal (nginx -s reload) or the response comes from another node behind a load balancer.
no alternative certificate subject name matches target host name (curl, code 60). The name is not in SAN. Common reasons: connecting to www. while the certificate is only for the bare domain; connecting without SNI or by IP; the default certificate of another site is served on this server.
self-signed certificate / self-signed certificate in certificate chain (codes 18 and 19). A self-signed certificate, or the chain ends with a root the client does not trust. If the issuer is named after an antivirus or corporate gateway, the traffic is being decrypted on the way.
tlsv1 alert protocol version, unsupported protocol (curl, code 35). No common TLS version: either an old client (only TLS 1.0) and a modern server, or vice versa. Check with --tlsv1.2 --tls-max 1.2 and -tls1_2 in s_client.
sslv3 alert handshake failure (code 35). No common cipher or group, or the server requires a client certificate. In s_client output with -state you can see at which step the handshake failed.
Connection reset by peer immediately after ClientHello. The server or a device on the path reset the connection upon seeing the ClientHello. If connection by IP (without SNI) succeeds but with the hostname it fails, this is a sign of SNI-based filtering on the path. It also happens on corporate firewalls that do not accept a large ClientHello with a post-quantum group X25519MLKEM768: check with -groups x25519 in s_client or --curves x25519 in curl.
SSL routines::wrong version number in s_client. You connected with TLS to a port that doesn’t speak TLS: for example, port 80, or 587 where you need -starttls smtp, or a server on 443 answering plain HTTP.
Browser opens, but curl or an app does not. Almost always an incomplete chain: browsers reconstruct missing intermediate certificates themselves, libraries do not. Check: openssl s_client -showcerts and the Certificate chain line.
Diagnostic steps
curl -sv -o /dev/null https://name/— does it reach TLS, at which step is the error, curl exit code.openssl s_client -connect name:443 -servername name— chain andVerify return code.openssl x509 -noout -dates -ext subjectAltName— validity and names.curl --resolve name:443:IP— do all servers behind the load balancer behave the same.testssl.sh name— versions, ciphers and a full configuration check.- If nothing helped — capture traffic and use Wireshark with
SSLKEYLOGFILE.
// Similar task
If you are dealing with something similar
This article belongs to one of the main working topics. You can keep reading on the topic, go to the homepage to understand what I do, or open the service pages directly.
Article topic
Security and protection
SSL, hardening, access control, service protection, and secure configurations.
Typical tasks behind this topic
- Set up SSL, certificates, and secure connections
- Restrict access and close unnecessary entry points
- Harden server and service configuration
// Next step
If you need help with this topic, not just another article, it is better to go straight to the service page. The homepage and topic collection stay available as secondary routes.
Open services// Contact
Need help?
Get in touch with me and I'll help solve the problem
I reply within one business day (03:00-13:00 GMT)
Или оставьте заявку здесь:
// Related