Nginx代理Caddy时出现Bad Gateway错误的排查求助
现象
Nginx作为IPv4到IPv6的NAT代理,对接其他SSL上游服务正常,但对接Caddy时出现SSL握手失败。
Nginx错误日志
2022/09/29 00:01:22 [error] 231367#231367: *1098403 SSL_do_handshake() failed (SSL: error:14094438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:SSL alert number 80) while SSL handshaking to upstream, client: CLIENT_IP, server: DOMAIN, request: "GET / HTTP/2.0", upstream: "https://UPSTREAM_IPV6:443/", host: "DOMAIN"
Caddy调试日志
Sep 29 00:27:37 cat caddy[450]: {"level":"debug","ts":1664404057.3207386,"logger":"tls.handshake","msg":"no certificate matching TLS ClientHello","server_name":"","remote":"[NAT_IPV6]:40410","identifier":"UPSTREAM_IPV6","cipher_suites":[49196,49200,159,52393,52392,52394,49195,49199,158,49188,49192,107,49187,49191,103,49162,49172,57,49161,49171,51,157,156,61,60,53,47,255],"cert_cache_fill":0.0007,"load_if_necessary":true,"obtain_if_necessary":true,"on_demand":false}
Sep 29 00:27:37 cat caddy[450]: {"level":"debug","ts":1664404057.3210196,"logger":"http.stdlib","msg":"http: TLS handshake error from [NAT_IPV6]:40410: no certificate available for 'UPSTREAM_IPV6'"}
当前Nginx配置
server { listen 443; server_name DOMAIN; location / { proxy_pass_header Authorization; proxy_pass https://UPSTREAM_IPV6; proxy_ssl_verify off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; add_header 'Access-Control-Allow-Origin' '*'; proxy_http_version 1.1; proxy_set_header Connection “”; proxy_buffering off; client_max_body_size 0; proxy_read_timeout 36000s; proxy_redirect off; } ssl_certificate /etc/letsencrypt/live/DOMAIN/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/DOMAIN/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; }
版本信息
- Nginx版本:nginx/1.18.0
- Caddy版本:v2.5.2
问题原因
从Caddy日志可以明确:TLS握手阶段,Caddy尝试用自身IPv6地址UPSTREAM_IPV6匹配证书,而非请求的域名DOMAIN。这是因为Nginx与上游Caddy建立SSL连接时,未发送正确的SNI(Server Name Indication)信息——SNI是SSL握手阶段用来告知服务器目标域名的字段,Caddy依赖它选择对应证书。
注意:配置中的proxy_set_header Host $host仅作用于HTTP层,与SSL握手阶段的SNI无关。此外,配置里的proxy_set_header Connection “”;使用了中文全角引号,会导致配置解析异常。
解决方案
- 在Nginx的location块中添加
proxy_ssl_server_name on;,启用SNI转发,让Nginx在与上游Caddy建立SSL连接时,将请求域名作为SNI发送。 - 将中文全角引号替换为英文半角引号,修复
proxy_set_header Connection的配置错误。
修改后的关键配置片段:
location / { proxy_pass_header Authorization; proxy_pass https://UPSTREAM_IPV6; proxy_ssl_verify off; proxy_ssl_server_name on; # 新增:启用SNI转发 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; add_header 'Access-Control-Allow-Origin' '*'; proxy_http_version 1.1; proxy_set_header Connection ""; # 修复引号格式 proxy_buffering off; client_max_body_size 0; proxy_read_timeout 36000s; proxy_redirect off; }
内容的提问来源于stack exchange,提问作者xeruf

