Nginx reverse proxy: SSL alert number 40
This reverse proxy usually uses an upstream server, which is the one that actually contains the content being served. Sometimes connectivity problems may occur against the latter. In this case we will receive errors that will show up in the system log with the string.
2021/10/10 10:22:14 [error] 2214955#2214955: *231 SSL_do_handshake() failed (SSL: error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:SSL alert number 40) while SSL handshaking to upstream, client: 127.0.0.1, server: www.example.com, request: "GET /uripath HTTP/2.0", upstream: "https://192.168.1.100/", host: "www.example.com"
2021/08/30 10:22:14 [debug] 2214955#2214955: *231 http upstream ssl handshake: "/uripath?"
2021/08/30 10:22:14 [debug] 2214955#2214955: *231 http next upstream, 2
2021/08/30 10:22:14 [debug] 2214955#2214955: *231 free rr peer 6 4
2021/08/30 10:22:14 [warn] 2214955#2214955: *231 upstream server temporarily disabled while SSL handshaking to upstream, client: 127.0.0.1, server: www.example.com, request: "GET /uripath TP/2.0", upstream: "https://192.168.1.100/", host: "www.example.com"
If we examine this event from the machine running the reverse proxy, against the content server, we can see the following:
openssl s_client -connect 192.168.1.200:443
CONNECTED(00000003)
140542033261888:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../ssl/record/rec_layer_s3.c:1543:SSL alert number 40
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 283 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
But if we include the domain name:
openssl s_client -connect 192.168.1.100:443 -servername external.example.com
CONNECTED(00000003)
depth=2 C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
verify return:1
depth=1 C = US, O = "Cloudflare, Inc.", CN = Cloudflare Inc ECC CA-3
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = "Cloudflare, Inc.", CN = sni.cloudflaressl.com
verify return:1
---
Certificate chain
0 s:C = US, ST = California, L = San Francisco, O = "Cloudflare, Inc.", CN = sni.cloudflaressl.com
i:C = US, O = "Cloudflare, Inc.", CN = Cloudflare Inc ECC CA-3
1 s:C = US, O = "Cloudflare, Inc.", CN = Cloudflare Inc ECC CA-3
i:C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
---
Server certificate
-----BEGIN CERTIFICATE-----
...
Which comes to mean that our nginx instance is not finding a default certificate installed on the web server with the content. So it is necessary to use SNI and specify the server name. Therefore, the location directive should look similar to the following:
location = / {
proxy_ssl_server_name on;
proxy_pass https://external.example.com/;
}
Being the key line proxy_ssl_server_name on;