[GH-ISSUE #4239] nginx 转发请求到 frps 的 HTTPS 时报错 502 #3337

Closed
opened 2026-05-05 14:09:14 -06:00 by gitea-mirror · 4 comments
Owner

Originally created by @ks4na on GitHub (May 24, 2024).
Original GitHub issue: https://github.com/fatedier/frp/issues/4239

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
Originally created by @ks4na on GitHub (May 24, 2024). Original GitHub issue: https://github.com/fatedier/frp/issues/4239 ### Bug Description 由于 frp 内网穿透的 HTTPS 服务,需要携带 `vhostHTTPSPort` 指定的端口号(例如:`20443`),所以想要在 frps 所在的机器上通过 nginx 反向代理来隐藏端口号。 所以就有了如下的 `nginx` 请求转发配置(`*.frps.example.com` 已经配置了解析到当前服务器): ```conf 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` 中的配置: ```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 反向代理的日志输出如下: ```sh 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`): ```sh 2024/05/23 15:23:01 [D] [vhost.go:216] http request for host [] path [] httpUser [] not found ``` 对比携带 `20443` 端口的请求时的日志输出: ```sh 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`: ```toml bindPort = 20000 vhostHTTPPort = 20080 vhostHTTPSPort = 20443 ``` `frpc.toml`: ```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 1. 2. 3. ... ### Affected area - [ ] Docs - [ ] Installation - [ ] Performance and Scalability - [ ] Security - [ ] User Experience - [ ] Test and Release - [ ] Developer Infrastructure - [ ] Client Plugin - [ ] Server Plugin - [ ] Extensions - [ ] Others
Author
Owner

@ks4na commented on GitHub (May 24, 2024):

翻看了相关 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时的一些细节和原理

<!-- gh-comment-id:2128514171 --> @ks4na commented on GitHub (May 24, 2024): 翻看了相关 issue,发现似乎并没有比较好的解决方案: - #610 中设置 proxy_pass 为域名,这样要走DNS,并且防火墙要开端口,并且写死了访问域名无法适应泛域名;作者回复可以使用 `proxy_ssl_server_name on;`,但是只加上这个配置也没有效果 - #671 中的解决方案需要本机搭建 dnsmasq 其他一些相关 issue ( #359, #520 )也没有给出解决方案。 查看源码 + 询问 chatgpt ,最终找到了解决方案,在此记录一下,方便后续有人出现同样问题时参考。 从源码找到获取 `host` 的位置:[https.go 48 行](https://github.com/fatedier/frp/blob/dev/pkg/util/vhost/https.go#L48) 的 `reqInfoMap["Host"] = clientHello.ServerName`。 询问 chatgpt ,说是需要添加额外 nginx 配置以在反向代理请求时保留并传递客户端提供的 SNI 信息: ```conf 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](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_name) ,发现默认值是 `$proxy_host`,By default, the host part of the [proxy_pass](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) URL is used. 由于我这里 proxy_pass 填写的是 127.0.0.1,所以无法获取到 `host`,而chatgpt 的答案中 `proxy_ssl_name` 设置为客户端请求的主机名 `$host`,所以 frp 可以正常获取到 `host` 。 贴一篇博客供参考 - [Nginx反向代理,当后端为Https时的一些细节和原理](https://blog.dianduidian.com/post/nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%BD%93%E5%90%8E%E7%AB%AF%E4%B8%BAhttps%E6%97%B6%E7%9A%84%E4%B8%80%E4%BA%9B%E7%BB%86%E8%8A%82%E5%92%8C%E5%8E%9F%E7%90%86/)
Author
Owner

@shyandsy commented on GitHub (Dec 10, 2024):

数据请求确实到了我内网,也到了我目标端口6443
但还是不通,哦 kubectl是双向加密鉴权证书,这个方法可能行不通

image

<!-- gh-comment-id:2531778308 --> @shyandsy commented on GitHub (Dec 10, 2024): 数据请求确实到了我内网,也到了我目标端口6443 但还是不通,哦 kubectl是双向加密鉴权证书,这个方法可能行不通 ![image](https://github.com/user-attachments/assets/d42c3ea0-63ab-4846-8ceb-cc6da1f10a95)
Author
Owner

@MinionTim commented on GitHub (Dec 12, 2024):

这种改法成功与否,还跟nginx的版本有关。我在nginx/1.22.1 上运行,一直报错:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:SSL alert number 40) while SSL handshaking to upstream。
但是换成 nginx/1.27.3 和 nginx/1.23.4 这两个版本却都成功了,对应的系统分别是Debian12、Debian11。

<!-- gh-comment-id:2539544195 --> @MinionTim commented on GitHub (Dec 12, 2024): 这种改法成功与否,还跟nginx的版本有关。我在nginx/1.22.1 上运行,一直报错:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:SSL alert number 40) while SSL handshaking to upstream。 但是换成 nginx/1.27.3 和 nginx/1.23.4 这两个版本却都成功了,对应的系统分别是Debian12、Debian11。
Author
Owner

@Z46I4W5476SJA commented on GitHub (May 8, 2025):

翻看了相关 issue,发现似乎并没有比较好的解决方案:

  • NGINX HTTPS and FRPS #610 中设置 proxy_pass 为域名,这样要走DNS,并且防火墙要开端口,并且写死了访问域名无法适应泛域名;作者回复可以使用 proxy_ssl_server_name on;,但是只加上这个配置也没有效果
  • nginx https转发frps  #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时的一些细节和原理

哇 兄弟 上班研究了一天 总是失败 直到看到你这条 加上了sni的配置 可以了 太感谢了

<!-- gh-comment-id:2862152750 --> @Z46I4W5476SJA commented on GitHub (May 8, 2025): > 翻看了相关 issue,发现似乎并没有比较好的解决方案: > > * [NGINX HTTPS and FRPS #610](https://github.com/fatedier/frp/issues/610) 中设置 proxy_pass 为域名,这样要走DNS,并且防火墙要开端口,并且写死了访问域名无法适应泛域名;作者回复可以使用 `proxy_ssl_server_name on;`,但是只加上这个配置也没有效果 > * [nginx https转发frps  #671](https://github.com/fatedier/frp/issues/671) 中的解决方案需要本机搭建 dnsmasq > > 其他一些相关 issue ( [#359](https://github.com/fatedier/frp/issues/359), [#520](https://github.com/fatedier/frp/issues/520) )也没有给出解决方案。 > > 查看源码 + 询问 chatgpt ,最终找到了解决方案,在此记录一下,方便后续有人出现同样问题时参考。 > > 从源码找到获取 `host` 的位置:[https.go 48 行](https://github.com/fatedier/frp/blob/dev/pkg/util/vhost/https.go#L48) 的 `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](https://github.com/fatedier/frp/issues/610) 中作者回复中提到过使用 `proxy_ssl_server_name on;`,但是当时添加了测试无效,于是又查了一下 [nginx 文档 proxy_ssl_name](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_name) ,发现默认值是 `$proxy_host`,By default, the host part of the [proxy_pass](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) URL is used. > > 由于我这里 proxy_pass 填写的是 127.0.0.1,所以无法获取到 `host`,而chatgpt 的答案中 `proxy_ssl_name` 设置为客户端请求的主机名 `$host`,所以 frp 可以正常获取到 `host` 。 > > 贴一篇博客供参考 - [Nginx反向代理,当后端为Https时的一些细节和原理](https://blog.dianduidian.com/post/nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%BD%93%E5%90%8E%E7%AB%AF%E4%B8%BAhttps%E6%97%B6%E7%9A%84%E4%B8%80%E4%BA%9B%E7%BB%86%E8%8A%82%E5%92%8C%E5%8E%9F%E7%90%86/) 哇 兄弟 上班研究了一天 总是失败 直到看到你这条 加上了sni的配置 可以了 太感谢了
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/frp#3337
No description provided.