How to Fix SSL_do_handshake() failed error in NGINX
You’re running NGINX as a reverse proxy. Your app works locally, but when connecting to an upstream over HTTPS, you get this error in your logs:
2025/09/19 16:22:38 [error] 2400768#2400768: *14601717 SSL_do_handshake() failed (SSL: error:0A000410:SSL routines::ssl/tls alert handshake failure:SSL alert number 40) while SSL handshaking to upstream, client: 127.0.0.1, server: , request: "GET / HTTP/1.1", upstream: "https://162.159.140.245:443/", host: "127.0.0.1:8081"
Let’s break this down and show you how to fix it.
What Does This Error Mean?
This error happens during the SSL handshake between NGINX and the upstream HTTPS server.
Specifically:
SSL_do_handshake() failed
: The handshake couldn’t be completed.alert handshake failure
: The upstream server rejected the connection.SSL alert number 40
: This alert code means Handshake Failure.
💡 In short: NGINX tried to connect to an HTTPS upstream, but the server didn’t like something about the SSL/TLS negotiation.
Common Causes of SSL Handshake Failures in NGINX
Here are the most common reasons this happens:
1. TLS Version Mismatch
The upstream server might require TLS 1.2 or TLS 1.3, but NGINX could be using an older version.
2. Cipher Suite Incompatibility
The cipher suites supported by NGINX and the upstream server may not overlap.
3. Missing SNI
Some upstream servers require SNI (Server Name Indication). If not provided, the handshake fails.
4. Invalid Certificates or Trust Issues
If NGINX does not trust the upstream server's certificate, it may block the handshake.
5. Self-signed Certificate or Expired Cert
The upstream server may use a self-signed or expired cert. NGINX rejects these unless configured to allow them.
How to Fix It
Here are practical steps to resolve this issue:
✅ Step 1: Add proxy_ssl_server_name on;
Enable SNI for the proxy connection:
location / {
proxy_pass https://162.159.140.245;
proxy_ssl_server_name on;
}
This tells NGINX to send the Host
name in the TLS handshake, which some CDNs or secure servers require.
✅ Step 2: Set the Correct proxy_ssl_name
You’re connecting to an IP, but the server expects a hostname (used in its SSL certificate).
Add this:
location / {
proxy_pass https://162.159.140.245;
proxy_ssl_server_name on;
proxy_ssl_name example.com; # Replace with correct domain
}
This ensures NGINX presents the correct SNI in the TLS handshake.
✅ Step 3: Adjust TLS Protocols (if needed)
In some rare cases, you may need to tweak the TLS versions:
proxy_ssl_protocols TLSv1.2 TLSv1.3;
You can also specify allowed ciphers:
proxy_ssl_ciphers HIGH:!aNULL:!MD5;
✅ Step 4: Skip SSL Verification (for internal/test use only)
If you're dealing with a self-signed cert and you trust the upstream:
proxy_ssl_verify off;
⚠️ Use this only in dev/staging. In production, always validate SSL certs.
Testing the Connection Manually
You can test the SSL handshake using openssl
:
openssl s_client -connect 162.159.140.245:443 -servername example.com
Check if the handshake succeeds and review the certificate chain.
Real-World Scenario: Proxying to a CDN or API
Many services like Cloudflare, AWS, or Gcore expect the correct Host
and TLS SNI when accepting HTTPS requests.
If you're using NGINX to proxy traffic to such services, you must:
- Enable
proxy_ssl_server_name
- Set
proxy_ssl_name
to the domain name - Ensure certificate trust is configured
Avoid On-Call Headaches with Akmatori
Tired of wasting time debugging production issues like SSL handshakes?
Try Akmatori, the AIOps platform designed to:
- Catch and alert on SSL errors in real time
- Automate incident resolution
- Reduce alert fatigue and downtime
Akmatori helps DevOps teams stay ahead of issues before users notice.
Run Your NGINX Setups Globally with Gcore
Need powerful and affordable servers to deploy your NGINX proxies?
Use Gcore to spin up VMs and bare metal across global regions. With high-speed networking and strong uptime, Gcore is perfect for edge and reverse proxy deployments.
Check out Gcore's VM and server pricing.
Final Thoughts
The error:
SSL_do_handshake() failed (SSL alert number 40)
... means a failed TLS negotiation between NGINX and your upstream.
In most cases, the fix is simple:
- Enable
proxy_ssl_server_name on;
- Set the correct
proxy_ssl_name
- Use valid SSL certs
- Tune TLS versions and ciphers if needed
Follow the steps above, and you’ll restore secure upstream connectivity fast.
Got more NGINX errors to debug? Let us know! And don’t forget to check out:
Happy proxying!