一次 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) 链路失败 |
| frps 日志 | [D] [vhost/vhost.go:218] http request for host [] path [] httpUser [] not found | 没有要访问的主机名被frps解析 |
常见 502 原因包括:
- 上游进程未监听 / 端口错(排除:端口正常)
- 网络不通(排除:frp 隧道其他 HTTP 服务可通)
- 证书/协议不兼容(重点:HTTPS + 多域)
- SNI / 域名路由未携带导致的错误证书或路由失败(高度可疑)
Github上同类问题
在 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://example.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://example.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 时,提供一个清晰、系统的排查范式。
欢迎继续交流改进思路。🚀