FRP:以内网服务安全暴露为目标的反向代理

先给结论

FRP(Fast Reverse Proxy)是一个面向内网穿透的高性能反向代理应用。它在具有公网 IP 的节点运行服务端 frps,在内网运行客户端 frpcfrpc 主动连接 frps 并登记代理规则,外部用户访问公网节点的端口或域名后,流量就能被转发至指定内网服务。它适合在已获授权的环境中发布 SSH、DNS、UDP、HTTP/HTTPS、开发预览和运维服务,而不是替代访问控制、网络分区或安全审计。^[来源:FRP v0.52.3 固定提交]

本文的配置和字段语义以 v0.52.3 的固定提交 44985f574dd3924e9cb48a969fddbd72b3afe2b3 为准。FRP 从 v0.52.0 起支持 TOML、YAML、JSON,INI 已被弃用;不同版本的配置字段、默认值和兼容性可能变化,部署前应以该版本的完整示例为准。^[来源:FRP 仓库 README]

完整配置的权威入口:frpc_full_example.tomlfrps_full_example.toml。它们是功能索引,不应未经最小化和安全审查就直接用于生产。

它解决什么问题

典型的家庭、办公室、云上私网或 Kubernetes 开发环境中的服务位于 NAT、私有地址或防火墙之后,公网无法直接发起连接。FRP 不要求公网用户能直连内网:内网的 frpc 先向公网 frps 建立出站控制连接;当用户到达 frps 暴露的端口或 HTTP 虚拟主机时,frps 再通过该已建立的客户端通道请求 frpc 连接本地服务。

公网访问者
    │  TCP / UDP / HTTP(S)

公网节点:frps ───── 控制连接与工作连接 ───── 内网节点:frpc ──── 本地服务
  监听 remotePort                                  主动出站             127.0.0.1:port
  或按 Host 路由                                                        或内网 IP:port

因此,FRP 的核心价值是:将“对内网服务的入站可达性”集中到一台可控的公网入口,同时将每项服务映射为明确的代理规则。它并不会自动让服务安全:公开 SSH、管理后台或 UDP 服务仍需最小暴露面、强认证、补丁、日志和速率限制。

组件、数据路径与主要协议

组件或概念角色要点
frps公网服务端接收 frpc 登录,监听代理端口,并将外部连接调度给对应客户端。
frpc内网客户端连到 frps,声明本地地址、端口和代理类型;可以运行在主机或容器中。
proxies发布规则每条规则把一个本地服务映射为公网端口、子域名或自定义域名。
visitors受限访问端用于 STCP/XTCP 等由另一台 frpc 访问的场景,不把服务直接公开为公网端口。
transport.protocolfrpcfrps 的承载传输v0.52.3 可选 tcpkcpquicwebsocketwss;这与某条代理规则是 TCP 还是 UDP 是两层不同概念。

FRP 的代理类型覆盖 TCP、UDP、HTTP、HTTPS、TCPMUX、STCP、XTCP 和 SUDP 等能力。最常见的 TCP/UDP 规则以 remotePort 监听公网端口;HTTP/HTTPS 按域名和路径做路由;STCP 与 XTCP 则强调只让持有密钥和对应 visitor 配置的另一端访问。HTTP/HTTPS 的域名路由和 TCP/UDP 的端口转发不应混为一谈。^[来源:FRP v0.52.3 frpc 完整示例]

为什么使用 FRP

  • 出站建连适配 NAT 场景:内网侧通常只需允许到公网入口的出站连接,无需为每个服务申请公网 IP。
  • 多种承载与代理协议:客户端到服务端可用 TCP、QUIC、KCP、WebSocket/WSS;服务发布层可转发 TCP、UDP、HTTP、HTTPS 等不同业务协议。
  • TCP 流式复用:同一 frpc 的逻辑连接可在一个 TCP 连接中复用,减少频繁建连造成的握手与延迟;两端的 transport.tcpMux 必须一致。它与 tnet 网络库 的事件循环或零拷贝 API 是不同层次的机制。^[来源:FRP README - TCP Stream Multiplexing]
  • 代理组负载均衡:同一 loadBalancer.group 且共享 groupKey 的多个代理可共同承载连接,适用于多个等价后端。
  • 端口复用和 HTTP 路由:HTTP(S) 可按域名、子域名、路径或 HTTP 用户路由;TCPMUX 可让多项服务共用入口端口,前提是协议与路由模式匹配。
  • 客户端插件与服务端扩展:原生客户端插件可提供静态文件、HTTP/SOCKS5 代理、协议转换等;服务端插件适合接入组织内的鉴权、审计或策略。
  • 管理 UI:服务端与客户端均可配置 dashboard;它应仅绑定受管网络并增加认证,不能直接裸露在公网。

P2P 不是“没有服务端”

FRP 的 xtcp 目标是在两个客户端之间建立直连的数据通道,从而避免业务数据长期经过 frps 中转,适合大流量或带宽昂贵的场景。frps 仍负责登录、协调和打洞;它不是一个完全去中心化系统。不同 NAT、防火墙、运营商 CGNAT 和 UDP 策略会导致直连失败,官方示例明确建议在 xtcp 不可用时回退至 stcp。^[来源:FRP README - P2P Mode]

实践中应把 xtcp 视为一个“可选的性能优化路径”,而不是可用性前提:准备 STCP 或普通中转规则作为回退;用 secretKey、visitor 绑定地址和网络策略限制访问;测试实际两端网络,而非只在同一局域网中验证。

示例:以 WSS 承载 FRP 控制连接并代理 UDP 服务

下面的例子将内网 127.0.0.1:12345/udp 发布为公网服务器的 12345/udpfrpc → frps 的控制与工作通道使用 wss,由 Caddy 在 443 上终止 TLS 并反向代理 WebSocket Upgrade 到 frps:7000。UDP 业务流量仍由 frps 监听的 12345/udp 接收——WSS 不会把 UDP 用户流量转换成 HTTPS

该结构可用于必须经受控 HTTPS/WSS 入口到达 frps 的授权网络环境;不得把它用于规避组织、运营商或平台的网络策略。生产环境中,Caddy 和 FRP 应位于同一私有 Docker 网络,7000 不应再直接发布到公网。

1. 内网侧 frpc

docker-compose.yml

services:
  frpc:
    image: ghcr.io/fatedier/frpc:v0.52.3
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
    volumes:
      - ./frpc.toml:/etc/frpc.toml:ro
    command: ["-c", "/etc/frpc.toml"]

frpc.toml

serverAddr = "frp.example.com"
serverPort = 443
 
transport.protocol = "wss"
 
auth.method = "token"
# 通过 secret、环境变量模板或受限挂载注入;不要提交真实值。
auth.token = "replace-with-a-strong-random-token"
 
[[proxies]]
name = "udp-service"
type = "udp"
localIP = "127.0.0.1"
localPort = 12345
remotePort = 12345

其中 serverAddr 必须解析到 Caddy 所在公网 IP,证书的域名也必须与之匹配;localIP = "127.0.0.1" 表示 frpc 与 UDP 服务在同一网络命名空间。若 UDP 服务在另一个 Docker 容器,改用其服务名或容器 IP,并将两个容器加入同一网络。

2. 公网侧 frps

docker-compose.yml

services:
  frps:
    image: ghcr.io/fatedier/frps:v0.52.3
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
    volumes:
      - ./frps.toml:/etc/frps.toml:ro
    command: ["-c", "/etc/frps.toml"]
    ports:
      - "12345:12345/udp"
    networks: [frp-internal]
 
networks:
  frp-internal:
    name: frp-internal

frps.toml

bindAddr = "0.0.0.0"
bindPort = 7000
 
# 要求客户端与 frps 之间启用 TLS。
tls.force = true
 
auth.method = "token"
auth.token = "replace-with-the-same-strong-random-token"

这里故意映射 7000:7000:Caddy 可通过同一 Docker 网络访问 frps:7000,而公网只看到 443 和业务 UDP 端口。防火墙也应只放行确实需要的 UDP 端口;不要使用宽泛的端口范围作为默认配置。

3. Caddy:WSS 入口

将 Caddy 与上面的 frp-internal 网络连接后,可使用:

frp.example.com {
    reverse_proxy frps:7000
}

Caddy 会识别并转发 WebSocket Upgrade;不必额外编写只匹配 Connection: Upgrade 的 matcher。域名需解析到该服务器,Caddy 需能申请或加载对应 TLS 证书。若 Caddy 与 FRP 分别由不同 Compose 项目管理,应把 frp-internal 声明为 external: true,并确认两个服务确实在同一个 Docker 网络中。

一个与 frps 同机、共享网络的 Caddy Compose 示例:

services:
  caddy:
    image: caddy:2.5.2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./data:/data
      - ./config:/config
    networks: [frp-internal]
 
networks:
  frp-internal:
    external: true

443/udp 是 Caddy 的 HTTP/3/QUIC 监听,并非 FRP 的 UDP 代理端口;本例的 UDP 服务仍要显式映射 12345:12345/udp。在具备 IPv6、CDN、四层负载均衡或独立 TLS 终止器时,还需分别检查 A/AAAA 解析、WebSocket Upgrade、证书和 UDP 转发是否完整。

可用性与安全检查清单

  1. 版本固定:镜像和配置按同一 FRP 版本验证;升级时先读 release note,再在测试环境验证 frpsfrpc 的组合。
  2. 强认证:为 auth.token 生成高熵随机值,放入 secret 管理或受限文件;不要提交到 Git、镜像层或日志。令牌泄露后立即轮换两端配置。
  3. TLS 与证书:开启并强制 TLS;对于 wss,校验客户端连接的是正确域名和受信任证书,而不是仅仅“443 能通”。
  4. 最小暴露面:只映射必要的 remotePort,本地服务优先监听 loopback;不要把 dashboard、bindPort 或内网管理端口无差别开放。
  5. 网络隔离:FRP 容器只加入需要的网络;Caddy 到 frps:7000 走私网;公网防火墙、云安全组和 Docker 端口发布规则保持一致。
  6. 应用层保护:FRP token 仅保护 FRP 客户端/服务端的登录,不等于业务用户身份认证。SSH 仍需要密钥与最小权限,Web 管理后台仍需要 SSO/MFA,UDP 协议仍需要自身鉴权与限流。
  7. 观测与回滚:记录 frpc/frps 日志、连接失败与端口监听;配置变更后分别从内网、本机和外网验证,再保留可回滚的上一版配置。

常见误解

误解更准确的说法
“有 token 就安全了。”token 只解决 FRP 端之间的认证;公开的应用仍可能有弱口令、漏洞或暴力破解风险。
“WSS 会把所有流量伪装为网页流量。”WSS 仅是 frpc 与入口之间的承载方式;UDP 代理的数据面仍按 UDP 端口接入,且使用前必须遵守网络策略。
“XTCP 一定不走服务器。”它仍依赖 frps 协调,且 NAT 打洞不保证成功;需要准备回退通道。
“配置全示例可以直接部署。”全示例用于发现字段,包含大量互斥或环境相关项;生产配置应只保留实际使用项。
“FRP 本身就是 VPN 或零信任系统。”FRP 是代理/穿透工具;若需求是用户、设备、网络段级访问控制,应结合 VPN、身份代理或零信任接入产品。

与已有知识的连接

FRP 与 tRPC 都涉及网络传输,但解决的问题不同:tRPC 是应用间 RPC 的协议、编解码和治理框架;FRP 是让受限网络中的既有服务可被指定入口访问的反向代理。若将 tRPC 服务通过 FRP 暴露,应分别评估 RPC 的身份认证、超时、限流和 FRP 的端口暴露、令牌、TLS 与网络策略,而不是把任一层的安全能力当作另一层的替代。

还可继续研究的问题

  • wss 与直接 TCP/QUIC 在目标网络、并发连接数和延迟下的可用性与资源开销差异是什么?
  • 对具体 UDP 服务而言,单包大小、MTU、丢包与 udpPacketSize 应如何通过压测确定?
  • 当多个客户端加入同一负载均衡组时,连接粘性、健康检查和故障切换是否满足业务协议语义?
  • 若目标是远程运维而非公开服务,STCP、XTCP、VPN 与身份代理各自的审计和权限边界如何取舍?