Problem

You run a request and curl stops with two words and a number:

curl: (35) SSL connect error

or a variant like curl: (35) OpenSSL SSL_connect: ... in connection to host:443. No page, no certificate warning, just a dead handshake. And here’s the trap: it looks like a certificate problem, so people reach for -k, it doesn’t help, and now they’re stuck. Error 35 is not about trust. It’s about two machines that couldn’t agree on how to speak TLS in the first place — and once you know that, the search space collapses.

What error 35 actually means

Curl’s internal name for it is CURLE_SSL_CONNECT_ERROR: a problem occurred somewhere in the TLS handshake. The key word is handshake. Before curl can send a byte of your request, the two ends run a negotiation — hello messages, an agreed protocol version, an agreed cipher, then the certificate exchange. Error 35 fires when that negotiation collapses before it finishes.

Put it next to its neighbor and the whole thing clicks:

  • Error 60 (CURLE_PEER_FAILED_VERIFICATION): the handshake worked. Curl received a certificate and decided it couldn’t trust it. This is a trust problem — missing intermediate, stale CA bundle, MITM proxy. It’s covered in the curl (60) guide.
  • Error 35: the handshake didn’t work. Curl never got a certificate it could evaluate, because the two ends couldn’t settle on a version or cipher, or the line died mid-negotiation.

That difference is the whole diagnosis. A 60 lives in your trust store; a 35 lives in the protocol layer. Which is why the reflex to slap -k on it is pure superstition here — -k turns off verification, and there was no verification step to reach.

What actually breaks the handshake

Nearly every 35 is one of these:

1. No common protocol version. The server requires TLS 1.2 or 1.3 and your local TLS library only offers 1.0/1.1 (or the mirror image: you force --tlsv1.3 against a server that maxes out at 1.2). Modern servers have been dropping TLS 1.0/1.1 for years, and an old OS ships an old OpenSSL that can’t reach 1.2+. The handshake ends before it starts.

2. No common cipher suite. Even with a shared version, the two ends have to share at least one cipher. A hardened server offering only modern AEAD suites and an ancient client offering only legacy ones have nothing in common, and the server answers the ClientHello with a fatal handshake_failure alert.

3. The connection is cut mid-handshake. A firewall, DPI middlebox, or misconfigured proxy resets the TCP connection during negotiation. Curl sees the handshake abort and reports 35 — even though nothing was wrong with either endpoint’s TLS. Corporate networks and aggressive “security” appliances do this constantly.

4. You’re talking TLS to a plaintext port (or vice versa). Hitting https://host:80, or a port that serves plain HTTP, means curl tries to start TLS and gets bytes that aren’t a TLS hello. The negotiation is nonsense from the first packet.

5. A stale or broken local TLS stack. An old, mismatched, or partially-upgraded OpenSSL/LibreSSL can fail handshakes that a current one completes fine — the classic “works everywhere but this one old box.”

How to read it: curl -v is the whole game

Re-run with -v and read the TLS lines. They tell you the version that was offered and the exact point the handshake died:

curl -v https://example.com

Look for the SSL connection using TLSv1.x line — or its absence. If you never see a negotiated version, the two ends never agreed on one. Then test your hypotheses by pinning:

  • curl --tlsv1.2 --tls-max 1.2 https://host — if forcing 1.2 works, a version-range mismatch was your problem.
  • Compare the same request from a different network (home vs office, or a cloud shell). If it works elsewhere, a middlebox on the first network was resetting the handshake — that’s cause #3, not the server.
  • Confirm you’re on the right port with a plain TLS probe; a 35 that’s really “wrong port” shows up instantly as garbage instead of a ServerHello.

Server-side or your side? Split it first

The single most useful move is deciding whether the fault is the server or your machine, because it sends you to opposite fixes:

  • Same failure from every client and network → the server’s TLS config is the issue (it requires a version/cipher your world can’t meet, or it’s misconfigured). Fix it there, or on your side raise your TLS library to meet it.
  • Fails only from one machine or one network → the problem is local: an outdated TLS library, or a middlebox/proxy on that network killing the handshake. Upgrading curl’s TLS stack or getting off that network is the fix, not touching the server.

An external check that completes a TLS handshake to the host from a neutral vantage point answers this in one shot: if the handshake succeeds from outside but fails from your box, the server is fine and the problem is between you and it.

Check it with DechoNet

  • SSL / TLS Check runs a handshake against the host from our network and reports the protocol versions and ciphers it actually negotiates. If it connects cleanly while your curl returns 35, the server is fine and your failure is local — a version-capped TLS library or a middlebox on your network.
  • Port Check confirms the port is open and speaking what you expect, which rules out the “TLS to a plaintext port” and “nothing is listening” causes before you go chasing ciphers.

Resolution Checklist

  • Re-run with -v and find the negotiated TLS version line — or confirm there isn’t one.
  • Pin --tlsv1.2/--tls-max 1.2 as a test. If it connects, you had a version-range mismatch; fix by upgrading your TLS library, not by living on the forced flag.
  • Run the same request from another network. Works elsewhere → a middlebox/proxy is resetting the handshake locally.
  • Verify the scheme matches the port — no https against a plaintext port, and confirm the port is actually open with Port Check.
  • If your OpenSSL/LibreSSL is old, upgrade curl and its TLS library. Do not reach for -k; it addresses error 60, never 35.

When to Escalate

  • If an external check negotiates TLS to the host cleanly but your machine still returns 35, stop touching the server — the fix is on your side (TLS library or network middlebox), and changing the server config will only break it for the clients that currently work.
  • If the failure is a mid-handshake reset on a corporate network, it’s a firewall/DPI policy, not something curl flags can override. That’s a conversation with whoever runs the network, not a client fix.
  • If pinning a version reveals the server tops out at TLS 1.0/1.1, the real problem is a server serving retired protocols — see ERR_SSL_VERSION_OR_CIPHER_MISMATCH, which is the browser’s name for the same negotiation failure.

Check your own domain now

Free, no sign-up. Runs the exact check this guide describes and shows what to fix.