Deep dive

Decoding curl SSL Errors: "certificate verify failed" and Beyond

curl is often the fastest way to isolate whether an SSL problem is genuinely server-side or specific to a particular client or browser — but only if you know how to read its specific error output rather than treating every SSL-related curl failure as the same generic problem.

Exit code 60 SSL certificate verify failed — chain or CAbundle issue Exit code 35 SSL connect error — handshake failed, oftenprotocol/cipher mismatch Exit code 51 Server certificate didn't match hostname (oldercurl versions) Exit code 58 Local certificate/key problem — usually aclient-cert (mTLS) issue
curl's exit codes map directly to specific failure categories

Exit code 60: certificate verify failed

This is the most common SSL-related curl error, and it means curl couldn't build a trusted chain from the server's certificate to a root it trusts. Run with -v for verbose output, which shows exactly which certificates curl received and where the chain building failed:

curl -v https://yourdomain.com

If the verbose output shows curl only received one certificate (your leaf certificate, no intermediates), that confirms a server-side incomplete chain — the fix belongs on the server, not in your curl invocation. If curl received a full chain but still failed, check whether curl's own CA bundle is outdated (particularly relevant on older Linux distributions or systems with an infrequently updated CA bundle package).

Exit code 35: SSL connect error

This indicates the handshake itself failed, generally before certificate details were fully exchanged — commonly a protocol version or cipher suite mismatch between your curl installation and the server. Testing with an explicit TLS version flag helps isolate this:

curl -v --tlsv1.2 https://yourdomain.com

If specifying a TLS version explicitly resolves the connection, your curl installation's default negotiated version isn't compatible with what the server offers — worth checking whether your curl/OpenSSL version is significantly outdated, since older installations sometimes default to attempting deprecated protocol versions first.

Exit code 58: local certificate problem

This specifically relates to a client certificate you're providing (relevant for mutual TLS/mTLS connections) rather than the server's certificate — check that the client certificate and key files you're passing via --cert and --key are valid, correctly formatted, and that the key actually matches the certificate.

Using curl to isolate server-side versus client-side issues

One of curl's most useful troubleshooting properties is that it uses its own independent trust store and TLS implementation, separate from any browser — if a certificate works fine in a browser but fails in curl, that's a meaningful signal in itself, since it usually points to an incomplete chain that a browser's more forgiving intermediate caching is masking, exactly the scenario covered in our related "works in Chrome, fails in API client" guide.

Never reach for -k as a permanent fix

curl's -k / --insecure flag disables certificate verification entirely — useful for quickly confirming whether SSL verification specifically is the blocker during active debugging, but never appropriate as a permanent fix in a script, cron job, or production integration, since it removes the actual security property TLS is providing.

What curl's -v flag reveals that the base error message doesn't

Adding -v to any curl command shows the full handshake exchange, including exactly which certificate was presented, which CA curl attempted to validate it against, and at what specific step the failure occurred — considerably more diagnostic detail than curl's default, terse one-line error message alone provides.

How curl's own bundled CA list differs from your operating system's trust store

curl on some systems uses its own bundled CA certificate list (via a ca-bundle file) rather than the operating system's trust store — an outdated bundled CA list can cause curl to report a trust error even when the same certificate validates correctly in a browser using the OS's more current trust store.

Why reproducing a curl error with a minimal, isolated test case speeds up diagnosis

Stripping away every unnecessary flag and option, testing with a bare `curl -v https://domain.com`, isolates whether a specific flag or option in your original, more complex command is actually contributing to the error, rather than the underlying TLS connection itself being the problem.

What SSL_ERROR_SYSCALL specifically indicates versus a clean handshake rejection

SSL_ERROR_SYSCALL typically means the underlying network connection dropped unexpectedly during the handshake, rather than the handshake completing and being cleanly rejected — this points toward a network-level cause (a firewall silently dropping the connection, an unstable network path) rather than a certificate or configuration problem specifically.

How curl's --cacert flag lets you test against a specific, known trust bundle

Explicitly specifying a CA bundle file with --cacert lets you test whether a connection validates correctly against a known-good, current trust bundle, isolating whether curl's own default bundle is the actual source of a trust-related error versus a genuine problem with the server's certificate chain.

Why curl errors sometimes differ between command-line curl and a language's curl-based library

A language binding built on libcurl (PHP's cURL extension, Python's pycurl, and similar) can be compiled against a different OpenSSL or CA bundle version than your system's standalone curl binary, meaning the exact same URL can produce different results between the two — checking both independently helps isolate whether an issue is specific to one particular binding's configuration.

What SSL_ERROR_WANT_READ and similar non-fatal codes actually mean

Some error codes that look alarming at first glance, like WANT_READ or WANT_WRITE, actually indicate normal, expected non-blocking I/O behavior rather than a genuine failure — these are specifically relevant when curl or a library is used in non-blocking mode, and shouldn't be treated the same way as a genuine handshake rejection.

How curl's exit codes map to specific categories of failure

curl returns a specific numbered exit code for different failure categories (35 for a generic SSL connect error, 51 for a certificate verification failure, 60 for a CA-related trust failure, among others) — checking curl's exit code directly, in addition to the printed error message, gives a reliable, scriptable way to distinguish failure categories in automated tooling.

Why testing the same URL with curl, a browser, and OpenSSL together builds a complete picture

Running the identical URL through curl, a browser, and OpenSSL's s_client independently triangulates the actual problem — if only curl fails while the others succeed, the issue likely lies in curl's specific configuration or bundled trust data rather than the server itself.

A quick reference for the most common curl SSL error codes

Exit code 35 generally means a generic connect error, often protocol negotiation failure; 51 means the peer certificate failed validation against the hostname; 60 specifically means the CA certificate couldn't be verified; 58 means a problem with a locally supplied client certificate — checking this against curl's own documentation resolves ambiguity fast.

Why this diagnostic approach transfers directly to other command-line HTTP tools

The same layered approach, checking the specific error code, adding verbosity, testing with an explicit CA bundle, isolating with a minimal reproduction, applies just as well to wget, httpie, or any other command-line HTTP client — the underlying TLS concepts don't change based on which specific tool is making the connection.

The short version: curl's specific exit codes and verbose (-v) output map directly to distinct failure categories — read them before guessing, and use curl's independence from any browser's caching behavior to confirm whether a certificate issue is genuinely server-side.