Command-line tools
curl: (35) SSL connect error: why the handshake failed
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to example.com:443curl: (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 -vstops right afterClient hello;openssl s_clientprintserrno=104orunexpected 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_PROXYset. 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 ashttps://when the proxy speaks plain HTTP causes the same error: usehttp://proxy:3128inHTTPS_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-tls1connects (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_clientshowsAcceptable client certificate CA namesand 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 = UnsafeLegacyServerConnectcan 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=0in the global config). It weakens every TLS connection on the machine. - Force
--tlsv1.0in 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.