一次 frp + nginx 内网穿透 502 (SNI) 故障的定位与原理解读
通过 nginx 进行反代,并经由内网穿透工具 frp 对内网的 HTTPS 服务进行代理时,如果遇到 502 错误,需要在 nginx 配置中添加:
让 nginx 在与 frps 建立 TLS 时正确携带域名,故障即消除。
1. 场景背景¶
我拥有:
- 一台【内网服务器】(运行目标业务 / HTTPS 服务)
- 一台【公网服务器】(运行
frps
+ nginx,负责对外域名解析与反代) - 使用 frps-frpc 做内网穿透,使公网能访问内网的 HTTPS 服务
上线后,浏览器访问域名,nginx 日志/页面出现 502 Bad Gateway。
2. 拓扑结构¶
2.1 失败时的(缺少 SNI)¶
sequenceDiagram
participant UA as 浏览器
participant N as 公网 nginx
participant S as frps
participant C as frpc
participant IS as 内网HTTPS服务
UA->>N: HTTPS 请求 (Host: example.imxcg.com)
N->>S: TLS ClientHello (无 SNI)
Note over S: 无法按域名选择正确证书/路由
S-->>N: 握手异常 / 错误证书
N-->>UA: 502 (上游握手失败)
3.2 现象与排查思路¶
观察点 | 现象 | 初步推断 |
---|---|---|
浏览器 | 返回 502 | 上游 (nginx->frps) 链路失败 |
nginx error.log | upstream prematurely closed / handshake error / 502 | TLS 建立阶段异常 |
frps 日志 | 可能出现握手失败或未命中期望路由 | 需要 SNI 区分域名 |
常见 502 原因包括:
- 上游进程未监听 / 端口错(排除:端口正常)
- 网络不通(排除:frp 隧道其他 HTTP 服务可通)
- 证书/协议不兼容(重点:HTTPS + 多域)
- SNI / 域名路由未携带导致的错误证书或路由失败(高度可疑)
在 GitHub issue fatedier/frp#4239 中找到了同类症状与解决方案 —— 指向 SNI 未启用。
3. SNI 原理速览¶
- SNI (Server Name Indication) 是 TLS 扩展 (RFC 6066),客户端在
ClientHello
中声明要访问的主机名。 - 服务器据此:
- 选择对应虚拟主机证书(多域/多证书共用同一监听端口)
- 或做基于域名的路由 / 分流(例如部分隧道、多租户场景)
- 如果客户端不发 SNI:
- 服务器可能只能返回“默认证书”,域名不匹配 → 证书校验失败
- 某些实现直接拒绝或无法匹配到正确后端
在本案例中:nginx 作为“客户端”访问 frps。在默认情况下(proxy_ssl_server_name off
),且 proxy_pass
指向的是 IP 地址 或你需要动态指定域名变量时,nginx 不会在上游握手里发送 SNI。frps 侧基于 SNI 选择证书/路由 → 失败 → 导致 502;浏览器直连之所以正常是因为浏览器始终发送 SNI。
4. nginx 关键配置差异¶
4.1 故障前(缺失 SNI 设置)¶
location / {
add_header X-Served-By $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass $forward_scheme://imxcg.com$request_uri;
}
4.2 修复后(显式启用 SNI)¶
location / {
# 关键配置
proxy_ssl_server_name on;
proxy_ssl_name $host;
add_header X-Served-By $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass $forward_scheme://imxcg.com$request_uri;
}
两行配置作用:
指令 | 作用 |
---|---|
proxy_ssl_server_name on; | 打开向上游发送 SNI 的功能 |
proxy_ssl_name $host; | 指定要发送的 SNI 名称(可用变量) |
注意:仅
proxy_ssl_server_name on;
时,SNI 默认值是proxy_pass
里解析出的主机名;若上游是 IP 或你需要保持与客户端 Host 完全一致,显式proxy_ssl_name $host;
更稳妥。
5. 为什么会是 502?¶
502 = nginx 作为网关,连接上游过程中**收到了无效响应或连接失败**。这里属于:
- nginx 尝试建立 TLS → 未发送 SNI
- frps 无法匹配正确域名 → 返回错误证书或直接中断
- nginx 认为上游握手失败 → 返回 502 给浏览器
如果你抓包(openssl s_client
/ tcpdump
)会看到第一版缺少 server_name
拓展,修复版则出现。
6. frp 在其中的角色¶
frps
与frpc
之间是**穿透隧道**,对它们来说只转发 TCP / TLS 流量(具体由配置的类型决定)- 当你把 nginx 反代指向
frps
的 HTTPS 端口 时,frps
需要充当“TLS 服务器”角色,基于 SNI 选择证书/路由 - 若使用多域名/多证书(你目录下存在
imxcg.cn
,imxcg.com
,imxcg.net
各自证书),更依赖 SNI 精确匹配
7. 常见延伸问题 FAQ¶
问题 | 说明 / 处理 |
---|---|
只单域名也要 SNI 吗? | 是。关闭 SNI 虽可能临时工作,但未来多域或证书轮换会踩坑。 |
可以用纯 HTTP 上游吗? | 可以,但内/公网之间可能明文;HTTPS 更安全(尤其含登录 / Token)。 |
为什么浏览器直连不会 502? | 浏览器天生发送 SNI;问题出在 nginx 作为“缺省不发 SNI 的客户端”。 |
proxy_ssl_name 必须吗? | 若 proxy_pass 使用域名且与 $host 相同,可以只开 proxy_ssl_server_name 。使用 IP 或需动态 Host 时建议显式。 |
仍 502 怎么办? | 抓包 / openssl / 查看 frps 日志,确认不是证书权限、frp 映射配置或防火墙导致。 |
8. 总结¶
关键点 | 收获 |
---|---|
故障类型 | 公网 nginx 反代 frps HTTPS 502 |
根因 | 上游 TLS 缺少 SNI,frps 无法正确匹配证书/路由 |
解决 | 添加 proxy_ssl_server_name on; proxy_ssl_name $host; |
原理 | SNI 让多域/多证书在同一监听端口上区分虚拟主机 |
经验 | 反代到多域 TLS 上游时明确启用 SNI;善用 openssl/curl 验证 |
小技巧
这类问题的定位关键词:nginx upstream 502 https sni frp
。第一时间检查是否发送 SNI 可以少走弯路。 若你也遇到同样的 502,不妨直接核对上面这一段是否缺失。
写到这里,整起事故链路已经闭环:问题再现 → 证据指向 SNI → 修改配置 → 验证通过 → 原理总结。希望这篇记录能在你下次遇到“看似玄学”的 502 时,提供一个清晰、系统的排查范式。
欢迎继续交流改进思路。🚀