frp 自建反向代理与公网暴露治理手册
选择 frp 等于接过整条公网链路
frp 由公网主机上的 frps 和内网或开发机上的 frpc 组成。frpc 主动连接 frps,注册代理,再由 frps 接收公网流量并转发到本地服务。这个模型不依赖第三方隧道平台的托管 Endpoint,却把公网主机、带宽、证书、升级、日志、滥用处置、DDoS 风险和 on-call 一并交给自建团队。
开始前先证明本地 upstream 正常,并确认两端二进制来自同一受控版本:
curl -i http://127.0.0.1:8080/health
frps --version
frpc --version版本差异可能表现为字段不识别、认证失败或传输能力不一致。升级时应把服务端与客户端兼容范围写进变更记录,不要让个人电脑自动更新 frpc,而长期无人升级 frps。新配置优先采用 TOML、YAML 或 JSON;INI 已进入弃用路径,新能力不会持续补充到旧格式。
配置先验证,再接触公网端口
frp 提供配置校验命令。它不能证明网络和权限一定正确,却能在启动前发现语法与字段问题:
frps verify -c frps.toml
frpc verify -c frpc.toml服务端的最小安全基线不能只有监听端口和共享 token。公网监听、业务端口、认证范围、代理数量、端口范围、连接池和 TLS 都需要明确:
bindAddr = "0.0.0.0"
bindPort = 7000
vhostHTTPPort = 8080
proxyBindAddr = "0.0.0.0"
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/run/secrets/frp-token"
auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
allowPorts = [{ start = 20000, end = 20100 }]
maxPortsPerClient = 4
transport.maxPoolCount = 5
transport.heartbeatTimeout = 90
transport.tls.force = true
log.to = "console"
log.level = "info"bindPort 接收 frpc 控制连接,不是业务 HTTP 端口;vhostHTTPPort 承载按 Host 路由的 HTTP 代理;proxyBindAddr 决定业务代理监听在哪些网卡。云安全组和主机防火墙应只放行真实需要的端口,不能因为 frp 会动态代理就开放整个端口范围。
allowPorts 限制客户端可申请的 TCP 或 UDP 远端端口,maxPortsPerClient 限制单客户端代理数量,transport.maxPoolCount 抑制过大的预建连接池。它们共同控制一名合法客户端能消耗多少公网资源。没有这些限制时,一枚共享 token 不只授予“连接”能力,也可能间接授予大量端口与连接资源。
认证控制客户端,不控制公网访客
auth.method = "token" 让 frps 验证 frpc。tokenSource 从文件读取凭证,避免把 token 写进可共享配置;additionalScopes 让心跳和新工作连接也携带认证。transport.tls.force = true 则要求客户端使用 TLS 建立控制连接。这些配置保护的是 frpc 到 frps 的控制与工作通道。
它们不保护访问公网业务端口的访客。一个陌生人请求 webhook.example.test 时,并不会提交 frpc token。HTTP 服务仍应依靠应用签名、前置身份代理、受控 Basic Auth、mTLS 或其他适合业务的访问策略。把 frp token 当作 Webhook 鉴权,会留下一个完全公开的业务入口。
共享 token 还会扩大爆炸半径。更稳妥的做法是按环境或租户拆分 frps 实例、网络边界与凭证,至少不要让个人开发机、CI 和长期演示环境共用一枚永不过期的 token。凭证文件应由专用服务账号读取,轮换时允许短暂双凭证或受控窗口,并验证旧客户端确实无法重新登录。
客户端只声明自己需要的代理
一个 HTTP 客户端配置可以写成:
serverAddr = "frp.example.test"
serverPort = 7000
user = "team-a"
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/run/secrets/frp-token"
auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
[[proxies]]
name = "webhook-dev"
type = "http"
localIP = "127.0.0.1"
localPort = 8080
customDomains = ["webhook.example.test"]
transport.bandwidthLimit = "1MB"
transport.bandwidthLimitMode = "server"serverAddr 与 serverPort 指向 frps 控制入口,localIP 与 localPort 是内网 upstream。customDomains 要求域名解析到 frps,并由请求 Host 选择代理。user 会为代理名称增加租户前缀,便于隔离名称和观察日志,但它不是完整的多租户授权系统。
bandwidthLimitMode = "server" 让带宽限制在 frps 侧执行,避免完全依赖客户端自律。带宽限制仍不能替代连接数限制、请求体上限、应用超时、应用限流和云主机出站预算。若配置多个代理,应逐个说明 owner、用途、数据级别、开放端口或域名以及到期时间,不能把一台开发机上的全部服务打包暴露。
字段语义与版本变化应分别核对官方的 frp 客户端配置、frp 服务端配置和认证说明。团队模板应固定责任边界与验证步骤,具体字段仍跟随受控版本复核。
用正反实验区分四种失败
先启动 frps,确认日志显示预期监听端口,再启动 frpc,观察代理注册。外部请求必须从不同网络发起,并为 HTTP 代理携带正确 Host:
curl -i --resolve webhook.example.test:8080:<frps-public-ip> \
http://webhook.example.test:8080/health随后执行受控反例。把 frpc token 文件换成错误值并重启,预期登录被 frps 拒绝,代理不应上线;恢复 token 后重新注册。停止本地 upstream 但保留 frpc,预期公网入口可达而 origin 失败。停止 frpc 后等待 transport.heartbeatTimeout 清理状态,预期代理离线,公网请求失败。
| 现象 | 优先查看 | 责任边界 |
|---|---|---|
| frpc 无法登录 | 两端版本、控制端口、TLS、token 与时间 | 控制连接 |
| 代理已注册但公网连接拒绝 | frps 监听、防火墙、安全组、DNS | 公网入口 |
| 公网可达但返回上游错误 | frpc 日志、本地 curl、localPort | 本地 upstream |
| 未授权访客能访问业务 | 前置身份与应用鉴权 | 业务访问控制 |
| 停止当前 frpc 后仍可访问 | 重复进程、systemd、容器、同名代理 | 生命周期 |
这组实验比“页面能打开”更有意义。它证明控制认证、代理注册、公网监听、upstream 和访客权限是五个不同边界,任何一层的成功都不能替代另一层。
公网服务器必须有明确的运维归属
frps 是公网基础设施,不是随手运行的辅助命令。系统服务应使用非 root 账号、只读配置和独立 secret,限制文件系统与网络权限,并把重启策略、资源上限和日志轮转写进服务定义。二进制来源、校验和、版本、升级窗口与回滚包应可追溯。
健康检查要覆盖控制端口、已注册代理数量、活跃连接、认证失败、心跳超时、带宽和目标端口占用。代理数突然增加可能是配置误发,也可能是 token 泄露;认证失败突增可能是攻击,也可能是轮换不一致。告警需要结合 owner 与变更窗口,不能只有“frps 进程还在”。
公网 IP、域名、云安全组、主机防火墙和证书也属于同一服务。没有人负责系统补丁、证书续期、流量账单和滥用投诉,就不应把自建 frp 提供成团队公共能力。所谓“没有平台费用”只意味着成本转移到了云资源与人的响应时间上。
日志和面板同样可能扩大暴露面
frps 与 frpc 日志适合记录代理名、连接状态、错误和字节数,不应记录业务 Authorization、Cookie 或完整请求体。日志目录要有大小限制、保留期限和访问权限;调高 debug 级别前先确认不会把内部地址和凭证扩散到集中日志。
若启用 dashboard 或管理接口,应绑定管理网络或回环地址,经独立身份代理访问。管理面会展示代理、流量和内部拓扑,不能与业务代理端口一起直接暴露在公网。监控采集凭证和 frp 控制 token 也要分开,避免只读观察能力拥有注册代理的权限。
容量规划从最坏的合法客户端开始
frps 的瓶颈可能出现在公网带宽、文件描述符、连接池、CPU、内存、云防火墙或单个慢 upstream。容量测试应模拟真实连接时长与请求大小,观察端到端延迟分位数、活跃连接、传输字节、重连、拒绝、代理注册数和系统资源。测试值来自目标 SLO 与压测结果,不应把一台空闲开发机的表现当作容量结论。
allowPorts、maxPortsPerClient、连接池限制与服务端带宽限制构成防滥用基线,但仍需云账单告警、系统资源限制和网络层防护。TCP 直通比 HTTP 虚拟主机更难在应用层约束,开放数据库或管理协议的风险尤其高;除非已有独立网络身份与最小访问策略,否则不应通过 frp 直接提供这类端口。
回收要覆盖客户端、服务端和外部依赖
关闭一个临时代理时,先删除第三方回调或将 DNS 切离,停止所有 frpc 实例,再确认 frps 已清理对应代理。随后撤销任务 token,删除不再使用的域名、证书、防火墙规则和端口允许范围;如果 frps 只为本次任务存在,还要停止服务、保留必要审计证据并回收云主机。
最后从外部网络请求旧域名或端口,预期失败;同时检查 frps 在线代理列表、systemd、容器和开发机进程,确认没有自动重启的副本。只停止当前终端可能留下后台 frpc,只删除 DNS 也可能留下可被直接 IP 访问的端口。
恢复记录应写清 frps 位置与版本、frpc owner、代理名、域名或端口、token 轮换结果、残留云资源和外部失败证据。选择 frp 的真正门槛不是会不会写 TOML,而是团队是否愿意长期承担这份可验证、可升级、可回收的公网基础设施责任。
