I have been trying to use nginx as a reverse proxy to one of my external REST GET API to debug latency issue b/w my golang service and the external service.
I can easily call that public URL via golang’s net/http library and get successful response, but when i use nginx as reverse proxy, nginx gives 502 Bad Gateway. In nginx-error.log file i get errors regarding SSL like below, but wonder how i dont get error while calling that API via golang net/http client.
P.S: I have cross checked that nginx was running by creating a simple /ping-nginx API which just returns 200 statusCode and pong-nginx as response.
ping.go
package main
import (
"fmt"
"log"
"net/http"
"strconv"
)
func main() {
http.HandleFunc("/ping-via-go-client", func(w http.ResponseWriter, r *http.Request) {
url := "https://flights-explorer.makemytrip.com/ping"
resp, err := http.Get(url)
var statusCode string
if err == nil {
statusCode = strconv.Itoa(resp.StatusCode)
}
fmt.Fprintf(w, "pong-via-go-client with statusCode:"+statusCode)
})
http.HandleFunc("/ping-via-nginx", func(w http.ResponseWriter, r *http.Request) {
url := "http://localhost/ping"
resp, err := http.Get(url)
var statusCode string
if err == nil {
statusCode = strconv.Itoa(resp.StatusCode)
}
fmt.Fprintf(w, "pong-via-nginx with statusCode:"+statusCode)
})
log.Fatal(http.ListenAndServe(":3003", nil))
}
response of curls:
➜ ~ curl http://localhost:3003/ping-via-go-client
pong-via-go-client with statusCode:200%
➜ ~ curl http://localhost:3003/ping-via-nginx
pong-via-nginx with statusCode:502%
nginx.conf
#user nginx;
daemon off;
worker_processes auto;
error_log /opt/logs/nginx-error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 250;
}
http {
log_format upstream_time '$time_local $status $remote_addr to:- $upstream_addr $request '
'uct:$upstream_connect_time uht:$upstream_header_time urt:$upstream_response_time '
'request_time:$request_time tid_header:$http_tid status:$upstream_cache_status '
'slot:$http_slot slot_time:$http_slotstarttime ttl_req:$http_ttl ttl_resp:$upstream_http_x_accel_expires '
'job_flag:$http_jobflag cookies:"$http_cookie" bytes_sent:$bytes_sent gzip_ratio:$gzip_ratio '
'"$http_referer" "$http_user_agent" $http_x_forwarded_for cur_time:$msec';
keepalive_timeout 85;
upstream flights-explorer.makemytrip.com {
server flights-explorer.makemytrip.com:443;
keepalive 30;
}
server {
listen 80;
access_log /opt/logs/nginx-access.log upstream_time;
location = /basic_status {
stub_status;
}
location /ping-nginx {
return 200 'pong-nginxn';
add_header Content-Type text/plain;
}
location /ping {
# proxy_buffering off;
proxy_pass https://flights-explorer.makemytrip.com$request_uri;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_ssl_verify off; # Disable SSL certificate verification
proxy_ssl_verify_depth 0;
#proxy_ssl_session_reuse on;
#proxy_socket_keepalive on;
proxy_connect_timeout 10s;
proxy_read_timeout 10s;
}
}
}
errors in nginx-error.log
2024/03/03 11:07:55 [error] 20078#0: *6 SSL_do_handshake() failed (SSL: error:0A000438:SSL routines::tlsv1 alert internal error:SSL alert number 80) while SSL handshaking to upstream, client: 127.0.0.1, server: , request: "GET /ping HTTP/1.1", upstream: "https://23.63.110.25:443/ping", host: "localhost"
2024/03/03 11:07:55 [warn] 20078#0: *6 upstream server temporarily disabled while SSL handshaking to upstream, client: 127.0.0.1, server: , request: "GET /ping HTTP/1.1", upstream: "https://23.63.110.25:443/ping", host: "localhost"
2024/03/03 11:07:55 [error] 20078#0: *6 SSL_do_handshake() failed (SSL: error:0A000438:SSL routines::tlsv1 alert internal error:SSL alert number 80) while SSL handshaking to upstream, client: 127.0.0.1, server: , request: "GET /ping HTTP/1.1", upstream: "https://23.63.110.67:443/ping", host: "localhost"
2024/03/03 11:07:55 [warn] 20078#0: *6 upstream server temporarily disabled while SSL handshaking to upstream, client: 127.0.0.1, server: , request: "GET /ping HTTP/1.1", upstream: "https://23.63.110.67:443/ping", host: "localhost"
2024/03/03 11:07:56 [error] 20078#0: *6 SSL_do_handshake() failed (SSL: error:0A000438:SSL routines::tlsv1 alert internal error:SSL alert number 80) while SSL handshaking to upstream, client: 127.0.0.1, server: , request: "GET /ping HTTP/1.1", upstream: "https://23.63.110.74:443/ping", host: "localhost"
2024/03/03 11:07:56 [warn] 20078#0: *6 upstream server temporarily disabled while SSL handshaking to upstream, client: 127.0.0.1, server: , request: "GET /ping HTTP/1.1", upstream: "https://23.63.110.74:443/ping", host: "localhost"
logs at nginx-access.log
03/Mar/2024:11:07:56 +0530 502 127.0.0.1 to:- 23.63.110.25:443, 23.63.110.67:443, 23.63.110.74:443 GET /ping HTTP/1.1 uct:-, -, - uht:-, -, - urt:0.131, 0.116, 0.107 request_time:0.354 tid_header:- status:- slot:- slot_time:- ttl_req:- ttl_resp:- job_flag:- cookies:"-" bytes_sent:314 gzip_ratio:- "-" "Go-http-client/1.1" - cur_time:1709444276.083
2
Answers
Finally found a solution.
This blog clearly explains the need of Servername (SNI) demanded by the upstream (it can be validated using openssl command) and after making changes like it recommended, it's working for me without any issue.
https://www.infiniroot.com/blog/1120/nginx-reverse-proxy-ssl-alert-number-40-while-ssl-handshaking-upstream
P.S: i am posting the link directly instead of explaining bcz the author has explained everything in details and worth the read.
@Steffen Ullrich had suggested this earlier but with the other two confs didn't let it work.
From the comments, you can try:
The
proxy_ssl_server_name on;
directive enables SNI support, allowing Nginx to present the correct virtual host to the upstream server. That mirrors what the Gohttp.Client
does by default.proxy_set_header Host $host;
makes sure the Host header passed to the upstream matches the original request, which might be required by the upstream server to correctly handle the request.proxy_ssl_verify off;
is useful only for debugging. You must have it enabled in production environments for security purposes: it bypasses SSL certificate verification, akin to what might happen in a Go client if certificate verification is not explicitly enforced.Although the comments mention that the API should respond in <1ms, network conditions or server processing times might still cause delays. Adjusting
proxy_connect_timeout
andproxy_read_timeout
appropriately can help, again, for debugging purposes.And enhance Nginx’s logging by setting
error_log /path/to/error.log debug;
to get more detailed debug information.The Go
http.Client
automatically handles many aspects of HTTP and SSL communication, including SNI and Host headers. It is possible that the default Go client’s handling of SSL, headers, orkeep-alives
differs in a way that is more compatible with the upstream server’s expectations.You can compare the actual request headers and SSL handshake details made by Go (using network sniffing tools like Wireshark or
tcpdump
to capture the traffic from your Go client to the server) and Nginx (via debug logs) to spot discrepancies.