Reliable TLS handshakes keep HTTPS traffic encrypted and authenticated, so handshake failures quickly turn into broken APIs, unavailable dashboards, or blocked automation. Inspecting the negotiation between client and server exposes protocol mismatches, expired certificates, and man‑in‑the‑middle devices that rewrite or terminate connections.
During a handshake, cURL passes the hostname, supported protocol versions, cipher suites, and extensions like SNI and ALPN to the TLS library, then validates the server certificate chain against local trust stores. Verbose and trace options show each phase of that process, including which side aborts the connection and which OpenSSL or other TLS-library error code explains the failure.
TLS debugging often involves private endpoints, internal certificate authorities, or client certificates that should not leak into logs or ticket systems. Trace files capture binary TLS records and sometimes HTTP headers or tokens, so storage locations deserve careful handling, and convenience options such as --insecure or ad‑hoc trust anchors must stay out of production scripts to avoid weakening security.
Related: How to use curl with CRT files
Related: How to ignore SSL certificate errors in cURL
Steps to debug TLS handshake with cURL:
- Open a shell with network access to the target HTTPS endpoint.
$ whoami root
Any environment with cURL in $PATH is sufficient, including Linux, macOS, and Windows terminals.
- Capture a baseline TLS negotiation using verbose mode to reveal the overall handshake flow.
$ curl --verbose --silent "https://www.example.com/" * Host www.example.com:443 was resolved. * IPv6: (none) * IPv4: 172.17.0.5 * Trying 172.17.0.5:443... * Connected to www.example.com (172.17.0.5) 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): ##### snipped #####
Lines beginning with TLSv1.x, Client hello, and Server hello confirm which protocol version and negotiation features reach the server before any error appears.
- Generate a detailed TLS trace file with timestamps to correlate failures with specific handshake records.
$ curl --trace-time --trace-ascii tls-trace.txt --silent "https://www.example.com/" $ head -n 6 tls-trace.txt 12:16:50.230498 == Info: Host www.example.com:443 was resolved. 12:16:50.230562 == Info: IPv6: (none) 12:16:50.230564 == Info: IPv4: 172.17.0.5 12:16:50.230577 == Info: Trying 172.17.0.5:443... 12:16:50.230674 == Info: Connected to www.example.com (172.17.0.5) port 443 12:16:50.232089 == Info: ALPN: curl offers h2,http/1.1 ##### snipped #####
The trace distinguishes outgoing and incoming TLS records, helping pinpoint whether the client, server, or intermediary sends the first alert or closes the connection unexpectedly.
- Test protocol compatibility by constraining allowed TLS versions and observing how the server responds.
$ curl --verbose --silent --show-error --tls-max 1.0 "https://modern.example.com/" * Trying 172.17.0.5:443... * Connected to modern.example.com (172.17.0.5) port 443 * ALPN: curl offers h2,http/1.1 * TLSv1.3 (OUT), TLS alert, protocol version (582): * OpenSSL/3.0.13: error:0A0000BF:SSL routines::no protocols available * Closing connection curl: (35) OpenSSL/3.0.13: error:0A0000BF:SSL routines::no protocols available $ curl --verbose --silent --tlsv1.2 --tls-max 1.2 "https://modern.example.com/" * Trying 172.17.0.5:443... * Connected to modern.example.com (172.17.0.5) port 443 * TLSv1.2 (OUT), TLS handshake, Client hello (1): ##### snipped #####
A protocol-version alert with weaker settings such as --tls-max 1.0 usually indicates that the server enforces newer standards and accepts only modern TLS versions.
- Narrow down cipher, key, or curve issues by restricting the cipher list to suites supported by current security policy.
$ curl --verbose --silent --tlsv1.2 --tls-max 1.2 --ciphers "ECDHE+AESGCM" "https://secure.example.com/" * Trying 172.17.0.5:443... * Connected to secure.example.com (172.17.0.5) port 443 * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / X25519 / RSASSA-PSS ##### snipped #####
Errors mentioning no shared cipher or handshake failure suggest that the server and client share no overlapping cipher suites, which often happens when legacy algorithms are disabled on only one side.
- Diagnose certificate-chain problems by reproducing failures with verbose output and checking the error string from the TLS library.
$ curl --verbose --silent --show-error "https://self-signed.example.internal/" * Trying 172.17.0.5:443... * Connected to self-signed.example.internal (172.17.0.5) port 443 * SSL certificate problem: unable to get local issuer certificate curl: (60) SSL certificate problem: unable to get local issuer certificate
Common messages such as unable to get local issuer certificate or self signed certificate in certificate chain indicate missing intermediate or private CA certificates on the client side.
- Supply the correct certificate authority bundle or directory to confirm whether missing trust anchors cause the handshake failure.
$ curl --verbose --silent --cacert /work/certs/selfsigned.crt "https://self-signed.example.internal/" * Trying 172.17.0.5:443... * Connected to self-signed.example.internal (172.17.0.5) port 443 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * CAfile: /work/certs/selfsigned.crt * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): ##### snipped #####
Options --cacert and --capath test whether updating trust stores or distributing the correct internal CA certificate resolves verification errors without permanently bypassing validation.
- Investigate mutual TLS issues by attaching a client certificate and private key to the request.
$ curl --verbose --silent --cert /work/certs/client.crt --key /work/certs/client.key --cacert /work/certs/ca.crt "https://mtls.example.com/" * Trying 172.17.0.5:443... * Connected to mtls.example.com (172.17.0.5) port 443 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Request CERT (13): ##### snipped #####
Handshake failures after sending a client certificate often relate to certificate expiration, incorrect key usage, or a subject that does not match server-side authorization rules.
- Use the --insecure flag only as a controlled diagnostic to confirm that failures come from certificate validation rather than protocol or cipher issues.
$ curl --verbose --insecure --silent "https://self-signed.example.internal/" * Trying 172.17.0.5:443... * Connected to self-signed.example.internal (172.17.0.5) port 443 * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / RSASSA-PSS > GET / HTTP/2 > Host: self-signed.example.internal ##### snipped #####
Flag --insecure disables hostname and certificate validation and exposes traffic to man-in-the-middle attacks, so its use belongs strictly to temporary debugging in controlled environments.
- Confirm a healthy handshake and HTTP response after any configuration change or server fix.
$ curl --verbose --silent "https://www.example.com/" * Trying 172.17.0.5:443... * Connected to www.example.com (172.17.0.5) port 443 * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / RSASSA-PSS * ALPN: server accepted h2 > GET / HTTP/2 > Host: www.example.com ##### snipped #####
Successful negotiation typically shows a selected TLS version and cipher suite plus a normal HTTP status such as HTTP/2 200 or HTTP/1.1 200 OK immediately after the handshake.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
Comment anonymously. Login not required.
