nginx 转发请求到 frps 的 HTTPS 时报错 502
ks4na opened this issue · comments
Bug Description
由于 frp 内网穿透的 HTTPS 服务,需要携带 vhostHTTPSPort
指定的端口号(例如:20443
),所以想要在 frps 所在的机器上通过 nginx 反向代理来隐藏端口号。
所以就有了如下的 nginx
请求转发配置(*.frps.example.com
已经配置了解析到当前服务器):
server {
listen 80;
listen [::]:80;
server_name *.frps.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name *.frps.example.com;
ssl_certificate certs/self_signed/server.crt;
ssl_certificate_key certs/self_signed/server.key;
include ssl/ssl_options.conf;
location / {
proxy_pass https://127.0.0.1:20443; # 转发到本机的 frps 监听 HTTPS 服务的地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
想要实现当访问 https://web-https.frps.example.com
时,转发请求到 frps 监听 HTTPS 服务的地址 https://127.0.0.1:20443
,然后根据 frpc.toml
中的配置:
user = "some_user"
serverAddr = "frps.example.com"
serverPort = 20000
# 内网 HTTPS 服务
[[proxies]]
name = "web-https"
type = "https"
localPort = 443
customDomains = ["web-https.frps.example.com"]
将请求转发到内网服务器。
但是这样做会报错 502
, nginx 反向代理的日志输出如下:
192.168.1.10 - - [23/May/2024:15:23:01 +0000] "GET /favicon.ico HTTP/2.0" 502 559 "https://web-https.frps.example.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" "-"
2024/05/23 15:23:13 [crit] 49#49: *29 SSL_do_handshake() failed (SSL: error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name:SSL alert number 112) while SSL handshaking to upstream, client: 192.168.1.10, server: web-https.frps.example.com, request: "GET / HTTP/2.0", upstream: "https://127.0.0.1:20443/", host: "web-https.frps.example.com"
报错的原因是 unrecognized name
。
frps
的日志输出如下(调整日志等级 log.level = debug
):
2024/05/23 15:23:01 [D] [vhost.go:216] http request for host [] path [] httpUser [] not found
对比携带 20443
端口的请求时的日志输出:
2024/05/23 15:28:34 [D] [vhost.go:247] [868cad529d201650] [some_user.web-https] new request host [web-https.frps.example.com] path [] httpUser []
发现少了 host
的值。
frpc Version
0.52.3
frps Version
0.52.3
System Architecture
linux/amd64
Configurations
frps.toml
:
bindPort = 20000
vhostHTTPPort = 20080
vhostHTTPSPort = 20443
frpc.toml
:
user = "some_user"
serverAddr = "frps.example.com"
serverPort = 20000
# 内网 HTTPS 服务
[[proxies]]
name = "web-https"
type = "https"
localPort = 443
customDomains = ["web-https.frps.example.com"]
Logs
No response
Steps to reproduce
...
Affected area
- Docs
- Installation
- Performance and Scalability
- Security
- User Experience
- Test and Release
- Developer Infrastructure
- Client Plugin
- Server Plugin
- Extensions
- Others
翻看了相关 issue,发现似乎并没有比较好的解决方案:
- #610 中设置 proxy_pass 为域名,这样要走DNS,并且防火墙要开端口,并且写死了访问域名无法适应泛域名;作者回复可以使用
proxy_ssl_server_name on;
,但是只加上这个配置也没有效果 - #671 中的解决方案需要本机搭建 dnsmasq
其他一些相关 issue ( #359, #520 )也没有给出解决方案。
查看源码 + 询问 chatgpt ,最终找到了解决方案,在此记录一下,方便后续有人出现同样问题时参考。
从源码找到获取 host
的位置:https.go 48 行 的 reqInfoMap["Host"] = clientHello.ServerName
。 询问 chatgpt ,说是需要添加额外 nginx 配置以在反向代理请求时保留并传递客户端提供的 SNI 信息:
server {
listen 80;
listen [::]:80;
server_name *.frps.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name *.frps.example.com;
ssl_certificate certs/self_signed/server.crt;
ssl_certificate_key certs/self_signed/server.key;
include ssl/ssl_options.conf;
location / {
proxy_pass https://127.0.0.1:20443; # 转发到本机的 frps 监听 HTTPS 服务的地址
# 设置 SNI 信息
proxy_ssl_server_name on;
# 设置 SNI 名称为客户端请求的主机名
proxy_ssl_name $host;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
解释:
proxy_ssl_server_name on;
:启用 SNI 传递。proxy_ssl_name $host;
:设置 SNI 名称为客户端请求的主机名。
添加上面的配置项,reload nginx 之后,确实可以正常访问了。
之前的 issue #610 中作者回复中提到过使用 proxy_ssl_server_name on;
,但是当时添加了测试无效,于是又查了一下 nginx 文档 proxy_ssl_name ,发现默认值是 $proxy_host
,By default, the host part of the proxy_pass URL is used.
由于我这里 proxy_pass 填写的是 127.0.0.1,所以无法获取到 host
,而chatgpt 的答案中 proxy_ssl_name
设置为客户端请求的主机名 $host
,所以 frp 可以正常获取到 host
。
贴一篇博客供参考 - Nginx反向代理,当后端为Https时的一些细节和原理