在使用 frp 的情况下,如何使用同一端口,不同子域名,反代内网的不同服务呢? 不使用 frp 的 vhost 服务。

思路一句话:把 frp 当成“带 Host 路由的 TCP 隧道”来用,而别去碰它专门给 HTTP(S) 准备的 vhost 端口。
具体做法就是用 tcpmux + httpconnect 模式:frps 只开一个普通端口(例如 7000)和一个 tcpmux 端口(例如 5002),frpc 里给每个子域名建一条 tcpmux 代理;frps 收到 CONNECT 请求后按 Host 头转发到对应的内网服务。全程不走 vhostHTTPPort/vhostHTTPSPort,也就绕开了 frp 自带的 vhost 功能。

1. frps(服务端)

frps.toml

bindPort = 7000          # 控制通道
tcpmuxHTTPConnectPort = 5002   # 唯一对外端口,所有子域名都连这里

2. frpc(客户端,每套服务一份)

例:把 git.example.com 转发到内网 192.168.1.10:22,db.example.com 转发到 192.168.1.20:3306

frpc-git.toml

serverAddr = x.x.x.x
serverPort = 7000
 
[[proxies]]
name = "git-ssh"
type = "tcpmux"
multiplexer = "httpconnect"
customDomains = ["git.example.com"]
localIP  = "192.168.1.10"
localPort = 22

frpc-db.toml

serverAddr = x.x.x.x
serverPort = 7000
 
[[proxies]]
name = "db-mysql"
type = "tcpmux"
multiplexer = "httpconnect"
customDomains = ["db.example.com"]
localIP  = "192.168.1.20"
localPort = 3306

3. DNS

*.example.com 解析到 frps 的公网 IP 即可。

4. 客户端怎么连

任何支持 HTTP CONNECT 的客户端都能用,统一指向 5002 端口,Host 头决定落到哪台内网机器。

  • SSH 示例(用 socat 当 CONNECT 代理)
ssh -o ProxyCommand='socat - PROXY:frps公网IP:%h:22,proxyport=5002' user@git.example.com
  • MySQL 示例
mysql --protocol=tcp -h db.example.com -P 3306 -u user -p \
      --ssl-mode=DISABLED \
      --default-auth=mysql_native_password

(MySQL 8 客户端默认走 SSL,先关掉 SSL 才能走纯 TCP 隧道)

要点回顾

  • 全程只用 5002 一个端口,不同子域名靠 Host 区分。
  • 没开 vhostHTTPPort/vhostHTTPSPort,也没用到 http/https 代理类型,完全符合“不用 frp vhost”的要求。

如果我使用 nginx 反代,那么该如何配置呢/

frp 只负责把“带 Host 头的 TCP 流”原封不动地送到 Nginx,Nginx 再按子域名反向代理到真正的内网业务。
这样——

  • 公网端口只用 frps 的 tcpmux 端口(下面示例用 7001);
  • 不启用 frp 的 vhost_http(s)_port;
  • 所有七层路由都由 Nginx 做,frp 仅当“透明隧道”。

一、frps(服务端)

frps.toml

bindPort = 7000          # 控制通道
tcpmuxHTTPConnectPort = 7001   # 唯一对外端口

二、frpc(客户端)

把 7001 收到的流全部转给“内网 Nginx”的 80 端口(或 443,如果你打算在 Nginx 里统一终结 TLS)。

frpc.toml

serverAddr = x.x.x.x
serverPort = 7000
 
[[proxies]]
name = "nginx-http"
type = "tcpmux"
multiplexer = "httpconnect"
customDomains = ["*.example.com"]   # 也可以逐条写
localIP  = "192.168.1.2"   # 内网 Nginx 地址
localPort = 80

三、Nginx(跑在内网 192.168.1.2)

  1. 先建一个“默认”server,专门接收 frp 隧道过来的流量:
# /etc/nginx/sites-available/frp-tunnel
server {
    listen 80;
    server_name _;          # 匹配所有 Host
    # 如果只想让 frp 过来的流量进这个 server,可再加
    # allow 192.168.0.0/16; deny all;
 
    # 按子域名分发
    include /etc/nginx/conf.d/subdomain_*.conf;
}
  1. 给每个子域名写独立配置(方便管理):
    /etc/nginx/conf.d/subdomain_git.conf
server {
    listen 80;
    server_name git.example.com;
 
    location / {
        proxy_pass http://192.168.1.10:3000;   # 真正的 GitLab/Gitea
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

/etc/nginx/conf.d/subdomain_db.conf

server {
    listen 80;
    server_name db.example.com;
 
    location / {
        proxy_pass http://192.168.1.20:8080;   # 数据库 Web 控制台
        proxy_set_header Host $host;
        ...
    }
}
  1. 如需 HTTPS,统一在 Nginx 里做 TLS 终结:
server {
    listen 443 ssl http2;
    server_name git.example.com;
    ssl_certificate     /etc/nginx/ssl/git.example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/git.example.com.key;
 
    location / {
        proxy_pass http://192.168.1.10:3000;
        ...
    }
}

frp 隧道依旧走 80,Nginx 内部 80→443 重定向即可。

四、DNS

*.example.com A 记录 → 公网 IP(frps 所在机器)。

五、客户端访问

浏览器直接 https://git.example.comhttp://db.example.com,流量路径:
用户 → 7001(tcpmux) → frps → frpc → Nginx(80/443) → 真正的内网服务。

六、小结

  • 公网只暴露 7001,子域名数量无限制;
  • frp 不参与七层路由,完全由 Nginx 按 server_name 分发;
  • 升级/新增子域名只需在 Nginx 里加配置,frp 端零改动。