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

> curl error 35 means the TLS handshake failed: protocol or cipher mismatch, a reset connection, plain HTTP on a TLS port, or a required client certificate.

Source: https://ssl.sh/errors/curl-35-ssl-connect-error/
Last updated: 2026-11-10

**`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:

```bash
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](https://ssl.sh/tools/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](https://ssl.sh/errors/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](https://ssl.sh/tools/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.
