Error fix · Server errors

nginx "SSL_do_handshake() failed": What Each Variant Means

Decode nginx SSL_do_handshake() failed log lines (wrong version number, no shared cipher, unknown ca, upstream handshakes) and fix client and proxy TLS settings.

Error messageSSL_do_handshake() failed (SSL: error:0A00010B:SSL routines::wrong version number) while SSL handshaking

By SiteDownCheck Editorial · Updated October 14, 2026 · 4 min read

nginx writes this to its error log when a TLS handshake does not complete. The part after SSL: is the OpenSSL reason, and it matters more than the prefix:

[crit] 1234#1234: *7 SSL_do_handshake() failed (SSL: error:0A00010B:SSL routines::wrong version number) while SSL handshaking, client: 203.0.113.9, server: 0.0.0.0:443

Many of these lines are harmless noise. Scanners, bots, old clients and people typing http:// against an HTTPS port all produce them. Treat the line as a real problem only if real users or your own upstream connections fail. Which side failed is the first question: is nginx the TLS server (the client: form above), or the client connecting to a backend (the line ends with while SSL handshaking to upstream)?

Variant 1: wrong version number

Someone sent non-TLS bytes to a TLS port. The usual causes are a plain HTTP request to port 443, a health check or load balancer configured for HTTP against an HTTPS listener, or a PROXY protocol mismatch. In OpenSSL 1.x the same condition reads error:1408F10B:SSL routines:ssl3_get_record:wrong version number. If the visitor sees "400 The plain HTTP request was sent to HTTPS port", that is nginx answering the same mistake politely. See SSL wrong version number.

Check that the listener flags match what the client speaks:

server {
    listen 443 ssl;          # TLS only
    # listen 443 ssl proxy_protocol;   # only if the LB sends PROXY protocol
}

Variant 2: no shared cipher or unsupported protocol

The client and server share no TLS version or cipher. Typical triggers: a very old client (TLS 1.0 or 1.1 only), or a server restricted to a narrow list.

ssl_protocols TLSv1.2 TLSv1.3;

Mozilla's SSL configuration generator publishes current recommended ssl_ciphers for each compatibility level; start there rather than inventing a list. Test what you serve with the TLS Version Checker and read SSL sslv3 alert handshake failure for client-side errors.

Variant 3: alert unknown ca / certificate unknown / bad certificate

SSL: error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca
SSL: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown

The client rejected your certificate and told nginx. The common reasons are a missing intermediate (an incomplete chain), a name that does not match, an expired certificate, or a client that does not trust the issuing CA. ssl_certificate must point to the full chain file, leaf first:

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

Run the SSL Checker and the SSL Chain Debugger. Related pages: incomplete certificate chain. If only some clients reject the certificate, the cause is often a missing intermediate. Browsers can fetch it themselves; command-line tools, Java and mobile apps cannot.

Variant 4: peer closed connection in SSL handshake

The client dropped the connection mid-handshake. It is often a scanner, a monitoring probe doing a bare TCP check, or a client that aborted after seeing your certificate. Sporadic entries are normal. A sudden flood from real client IPs suggests a certificate problem (see Variant 3).

With several certificates on one IP, nginx picks the certificate by the SNI name the client sends. Clients without SNI get the default (first) server's certificate, which produces name mismatches. Make the default server deliberate:

server {
    listen 443 ssl default_server;
    ssl_certificate     /etc/nginx/ssl/default.pem;
    ssl_certificate_key /etc/nginx/ssl/default.key;
    return 444;
}

Variant 6: handshake to an upstream

If the line ends while SSL handshaking to upstream, nginx is the client and your backend rejected it, and visitors get 502. Common fixes:

location / {
    proxy_pass https://backend.internal;
    proxy_ssl_server_name on;                 # send SNI to the backend
    proxy_ssl_name backend.internal;
    proxy_ssl_protocols TLSv1.2 TLSv1.3;
}

proxy_ssl_server_name is off by default, so backends that need SNI (including most hosts behind a CDN) fail until you turn it on. For verification of the backend certificate, set proxy_ssl_verify on with proxy_ssl_trusted_certificate. The visible symptom is covered in Fix 502 Bad Gateway.

Reproduce it from outside

openssl s_client -connect example.com:443 -servername example.com </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null

Look at Verify return code, the certificate chain and the negotiated protocol. Compare the output to the nginx error line to see which variant you hit. If you need to apply changes, validate with sudo nginx -t && sudo systemctl reload nginx.

Next steps

FAQ

Should I worry about occasional SSL_do_handshake failures?

Not usually. Scanners and bots trigger them constantly. Worry when real users fail, when the rate jumps suddenly, or when you see the upstream form.

Why does it say crit?

nginx logs handshake problems at that level even for benign client mistakes. You can filter by client: address and error text instead of the level.

Is wrong version number a server bug?

It almost always is a client or load balancer speaking plain HTTP to a TLS port. Fix the caller or the port.

Does this mean my certificate expired?

Not necessarily. Expired certificates normally show as alert certificate expired from the client side. Confirm with the SSL Checker.

Get notified when this fix updates

Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.

This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.