Command-line tools

curl: (35) SSL connect error: why the handshake failed

By · Last verified

Error messagecurl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to example.com:443

curl: (35) means the TLS handshake failed before curl could look at the certificate. The OpenSSL text after it is the clue: a reset connection, a port that does not speak TLS, a protocol or cipher mismatch, or a server that demands a client certificate.

Quick fix

The OpenSSL message in the verbose transcript identifies the cause.

Get the full handshake transcript:

curl -v https://example.com/ 2>&1 | sed -n '1,30p'
openssl s_client -connect example.com:443 -servername example.com -brief </dev/null

Then match the message:

Message Meaning
SSL_ERROR_SYSCALL, Connection reset by peer connection dropped mid-handshake
wrong version number the port is not speaking TLS
handshake failure, no protocols available, unsupported protocol no common TLS version or cipher
tlsv13 alert certificate required, alert handshake failure after a certificate request client certificate required
unsafe legacy renegotiation disabled old server without secure renegotiation

Check what the server supports with the SSL checker.

Causes

A reset connection, a port without TLS and a protocol or cipher mismatch are the common causes; a client certificate requirement is less common.

1. The connection is reset during the handshake

  • Confirm: curl -v stops right after Client hello; openssl s_client prints errno=104 or unexpected eof.
  • Fix: look at what sits in front of the server: a firewall or load balancer that drops TLS for unknown SNI names, a server block with no certificate for this hostname (see Nginx no ssl_certificate defined), or a proxy that needs HTTPS_PROXY set. Test from another network to separate local filtering from the server.

2. The port is not speaking TLS

  • Confirm: the message is wrong version number; curl http://example.com:443/ returns an HTTP response.
  • Fix: use the right URL scheme and port, or enable TLS on that listener (listen 443 ssl;). A proxy URL written as https:// when the proxy speaks plain HTTP causes the same error: use http://proxy:3128 in HTTPS_PROXY.

3. No common protocol version or cipher

Modern curl builds require TLS 1.2 or later by default via OpenSSL's security level, while old servers and embedded devices may only offer TLS 1.0/1.1 or weak ciphers.

  • Confirm: openssl s_client -tls1_2 ... fails but -tls1 connects (in a test build that still allows it).
  • Fix: upgrade the server's TLS configuration; the TLS config generator produces current settings. For a legacy device you cannot change, isolate it and use a dedicated client configuration, not a global downgrade.

4. Client certificate required

  • Confirm: openssl s_client shows Acceptable client certificate CA names and the handshake fails afterwards.
  • Fix: pass the certificate and key: curl --cert client.pem --key client.key https://example.com/.

5. Old server without secure renegotiation

OpenSSL 3 refuses servers that lack RFC 5746 secure renegotiation, with unsafe legacy renegotiation disabled.

  • Fix: update the server. As a temporary, local-only workaround, an OpenSSL config with Options = UnsafeLegacyServerConnect can be used for that one client.

Fixes by environment

Error 35 depends on the TLS library behind curl, so start with curl -V: Windows curl uses Schannel, and the system and Homebrew curl on macOS use different libraries.

Windows (Schannel): errors read schannel: next InitializeSecurityContext failed. If the detail mentions revocation, allow CRL/OCSP URLs through the proxy or use --ssl-revoke-best-effort.

macOS: the system curl and Homebrew curl use different TLS libraries; compare curl -V when one works and the other does not.

Docker: minimal images can carry curl builds without TLS support for some protocols; install the distribution's full curl package.

Behind a corporate proxy: set HTTPS_PROXY=http://proxy.example.com:3128 (note http://) and trust the proxy CA, otherwise you will move on to error 60.

Don't do this

  • curl -k. It does not fix error 35, because the failure happens before certificate checks.
  • Lower the OpenSSL security level system-wide (CipherString = DEFAULT@SECLEVEL=0 in the global config). It weakens every TLS connection on the machine.
  • Force --tlsv1.0 in scripts. It hides a server that needs upgrading and exposes the traffic to known protocol weaknesses.

FAQ

What is the difference between curl error 35 and 60?

Error 35 means the handshake did not complete. Error 60 means the handshake completed but the certificate could not be verified. A 60 is fixed with CA bundles or chains; a 35 is about protocols, ciphers, ports and network devices.

On Windows I see "schannel: next InitializeSecurityContext failed". Is that the same?

Yes, it is error 35 with the Windows Schannel backend. A common variant mentions that the revocation function could not check revocation, which happens when a proxy blocks CRL or OCSP access. Allow those URLs, or use --ssl-revoke-best-effort so curl only fails when revocation is confirmed.

Should I force an old TLS version to make it work?

Only to diagnose. If the server supports nothing newer than TLS 1.1, the fix belongs on the server: TLS 1.0 and 1.1 are deprecated by RFC 8996 and disabled by default in current OpenSSL security levels.

Sources

  1. libcurl error codes
  2. curl man page
  3. OpenSSL openssl-s_client