1 问题描述
- 项目技术栈:
Nginx -> K8S (service – Node Port) -> Springboot
- 问题描述:有一个提供给第三方的接口,用于接收他们发送的数据。第三方反馈他们在请求该接口时,始终收到 502 网关错误。但我们使用 Postman 测试接口时,一切正常。尝试在浏览器中直接访问该接口,收到 请求方法不被允许 的错误(因为接口期望的是 POST 请求)。
2 排查思路
502网关错误,优先排查Nginx配置问题,再排查网络策略问题,最后排查应用问题。
2.1 先排查Nginx
2.1.1 应用Nginx的配置
server {
listen 80 ;
listen 443 ssl http2 ;
server_name **.com.com;
index index.php index.html index.htm default.php default.htm default.html;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
access_log /www/sites/**.com.com/log/access.log main;
error_log /www/sites/**.com.com/log/error.log;
location ^~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
include /www/sites/***.com/proxy/*.conf;
if ($scheme = http) {
return 301 https://$host$request_uri;
}
ssl_certificate /www/sites/***.com/ssl/fullchain.pem;
ssl_certificate_key /www/sites/***.com/ssl/privkey.pem;
ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1 SSLv3 SSLv2;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
error_page 497 https://$host$request_uri;
proxy_set_header X-Forwarded-Proto https;
}
- 配置中有日志的地方,查看日志:
- access_log /www/sites/**.com.com/log/access.log main;
- error_log /www/sites/**.com.com/log/error.log;
没有报错日志! 没有请求日志!
- 检查 Nginx 的配置文件,确保服务器块的配置正确。
- 查看 Nginx 的访问日志和错误日志,发现 没有相关的请求日志,也 没有错误日志。
- 结论:请求可能 未到达 Nginx,需要进一步排查网络层面的问题。
2.2 排查服务器网络策略
检查防火墙和安全组设置:
- 确认服务器的 80 和 443 端口已开放,无网络策略拦截。
- 结论:网络策略未阻挡请求,问题可能不在网络层。
2.3 试用非公司网络访问
使用非公司网络访问
- 使用手机热点访问,访问正常!
- 使用其他省份网络访问(外省windows 浏览器访问,GET请求显示类型错误,nginx有日志),说明访问正常!
- 结论:问题可能与特定网络环境有关,但由于第三方仍然无法访问,需要继续排查服务器配置
2.4 重新检查Nginx配置
排查Nginx其他配置,查看配置中的的日志文件:
user root;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
error_log /dev/stdout notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';
server_tokens off;
access_log /var/log/nginx/access.log main;
access_log /dev/stdout main;
sendfile on;
server_names_hash_bucket_size 512;
client_header_buffer_size 32k;
# 其他配置
}
发现错误日志:
024/10/01 03:48:58 [error] 105#105: *11927 cannot load certificate "data:": PEM_read_bio_X509_AUX() failed (SSL: error:0909006C:PEM routines:get_name:no start line:Expecting: TRUSTED CERTIFICATE) while SSL handshaking, client: 111.11.111.111, server: 0.0.0.0:443
2024/10/01 03:48:58 [error] 105#105: *11928 cannot load certificate "data:": PEM_read_bio_X509_AUX() failed (SSL: error:0909006C:PEM routines:get_name:no start line:Expecting: TRUSTED CERTIFICATE) while SSL handshaking, client: 111.11.111.111, server: 0.0.0.0:443
2024/10/01 03:48:59 [error] 105#105: *11930 cannot load certificate "data:": PEM_read_bio_X509_AUX() failed (SSL: error:0909006C:PEM routines:get_name:no start line:Expecting: TRUSTED CERTIFICATE) while SSL handshaking, client: 111.11.111.111, server: 0.0.0.0:443
根据日志找到:“data:”相关的配置:
map "" $empty {
default "";
}
server {
listen 80;
listen 443 ssl http2;
server_name _;
ssl_ciphers aNULL;
ssl_certificate data:$empty;
ssl_certificate_key data:$empty;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
index 404.html;
root /usr/share/nginx/html;
}
配置说明:
- 这个配置是网站的默认配置,当访问的域名不在配置中时,会使用这个配置,这个配置是一个默认的配置,没有配置证书。
找到了问题原因:
- 表面错误:证书配置错误,导致SSL握手失败,导致502错误
- 根本原因:客户在请求时没有携带 SNI,导致Nginx找不到对应的证书,导致SSL握手失败,导致502错误!
3 SNI介绍
3.1 概念介绍
1. SNI 的作用
- SNI(Server Name Indication,服务器名称指示) 是 TLS 协议的一个扩展。它的主要作用是在建立 TLS/SSL 握手 时,客户端在 TLS 握手的早期阶段(具体来说,是在 Client Hello 消息中)告诉服务器它想访问的主机名(域名)。
2.为什么需要 SNI
- 在同一个 IP 地址和端口上托管多个 HTTPS 网站时,服务器需要知道客户端想访问哪个域名,以便提供对应的 SSL 证书。因为在 TLS 握手完成之前,HTTP 请求还没有开始,所以服务器无法通过 HTTP 头部(如 Host 头)来判断客户端想访问的域名。
3. SNI 的工作流程
- 客户端发起 TLS 连接,在 Client Hello 消息中包含 SNI 扩展,指明目标域名。
- 服务器收到 Client Hello,读取其中的 SNI 信息,根据域名选择相应的 SSL 证书,完成 TLS 握手。
- TLS 握手完成后,开始进行加密的 HTTP 通信。
4. 如果客户端不支持 SNI 或不提供 SNI 信息
- 服务器无法知道客户端想访问哪个域名,只能使用默认的 SSL 证书(通常是在 Nginx 配置中第一个被加载的服务器块,或者显式指定的默认服务器)。
- 如果默认服务器的 SSL 证书与客户端请求的域名不匹配,会导致证书错误,连接失败。
3.2 请求流程

- 域名解析与请求过程
- 域名解析:客户端首先通过 DNS 将域名解析为 IP 地址。
- 建立连接:客户端使用解析得到的 IP 地址发起 TCP 连接。
- TLS 握手:如果是 HTTPS 请求,客户端会在 TCP 连接建立后开始 TLS 握手。在这个阶段,客户端需要提供 SNI 信息(即请求的域名)。
- HTTP 请求:TLS 握手成功后,客户端才会发送加密的 HTTP 请求,包括 HTTP 头部信息(如 Host 头)。
- SNI 信息的位置
- SNI 信息是在 TLS 握手的 Client Hello 消息中,而不是在 HTTP 请求头中。因为在 TLS 握手完成之前,HTTP 请求还没有发送。
- HTTP 请求头中的 Host 头:用于在服务器上区分不同的虚拟主机,但这发生在 TLS 握手之后。
- 服务器如何处理没有 SNI 的请求
- 服务器无法根据 SNI 信息选择证书,只能使用默认的 SSL 证书。
- 如果默 认证书与请求的域名不匹配,客户端会收到证书错误,或者连接被拒绝。
3.3 为什么浏览器访问正常,Postman访问正常,但第三方代码请求接口不正常
- 浏览器访问正常:浏览器会携带 SNI 信息,所以 Nginx 可以根据请求的域名选择正确的 SSL 证书。
- Postman 访问正常:Postman 也会携带 SNI 信息。
- 第三方代码请求不正常:第三方代码可能没有携带 SNI 信息,导致 Nginx 无法选择正确的 SSL 证书,从而导致 SSL 握手失败。
4 解决方案
4.1 方法一:配置默认服务器的有效 SSL 证书
操作步骤:
- 修改 Nginx 默认服务器块,提供有效的 SSL 证书:
server {
listen 443 ssl http2 default_server;
server_name _;
ssl_certificate /www/sites/example.com/ssl/fullchain.pem;
ssl_certificate_key /www/sites/example.com/ssl/privkey.pem;
# 其他配置...
}
- 重启 Nginx 服务,使配置生效。
- 效果:客户端未提供 SNI 信息时,Nginx 使用默认的有效证书,完成 SSL 握手。
4.1 方法二:让客户端发送 SNI 信息
操作步骤:
- 与第三方沟通,确认他们的客户端是否支持 SNI。
- 如果不支持,建议他们升级客户端或修改代码,确保在 TLS 握手中发送 SNI 信息。