有一次我在外地出差,临时要连回办公区的一台开发机拿一份数据,那台机器在办公内网里,既没有公网IP,防火墙策略也动不了。折腾了一下午之后,我干脆在一台有公网IP的云主机上部署了 frp 服务端,在开发机上跑了一个 frp 客户端,几分钟之后远程连接就通了。从那以后,“内网穿透+FRP高可用集群”就成了我搭建远程访问通道的首选组合。
这篇文章适合两类人:一类是运维同学,内网里有大量系统需要远程维护,不能被单台服务器拖垮;另一类是开发者,想把家里的NAS、开发板或者办公网里的测试环境安全地暴露出来,又不想每天靠人工改端口映射过日子。我会从内网穿透的本质讲起,拆解 frp 的工作原理,然后给出一套完整可复现的双节点高可用集群方案,包括配置模板、故障切换实测和我在生产环境踩过的坑。整篇文章不绕弯子,给的是能直接拿去用的东西。
1. 内网穿透的底层逻辑与FRP的角色定位
1.1 为什么内网服务总是“够不着”
我们平时办公网络里的大部分设备,拿到的都是私有地址。这些地址只在公司内部有效,外部网络根本不知道它们的存在。即使你知道某台机器在内网里的IP,从公网发过去的流量也会在网络边界被丢弃,因为边界设备根本不知道应该把流量转给谁。
这是NAT(网络地址转换)带来的结构性限制。内网设备要上网,靠的是主动向外发起连接,边界设备会记住这个连接,并把回包送回来。但如果是外部主动发起连接,边界设备没有对应的映射记录,自然就不知道怎么处理。换句话说,整个内网默认是“只出不进”的。
要想让外部访问内网服务,思路无非两种:一种是在边界设备上主动开洞,把公网的某个端口映射给内网某台机器的某个端口;另一种是让内网设备自己主动向外建立一条通道,把外部流量“借道”引进来。frp 属于后者,这也是它在各种复杂网络环境下依然能工作的根本原因。
1.2 常见的打通方案与FRP的位置
在选型之前,先看一张表,把几种常见方案的适用场景理清楚:
| 方案 | 前提条件 | 优点 | 缺点 |
|---|---|---|---|
| 边界设备端口映射 | 有公网IP、能登录路由器/防火墙 | 无额外组件,延迟低 | 每次加服务都要改网络设备,动态IP环境下失效 |
| 企业专线/SD-WAN | 有预算、有专线接入 | 体验好,带宽大 | 成本高,部署周期长 |
| IPv6公网直连 | 运营商和网络设备支持 | 无需中转,天然公网可达 | 很多内网还是纯IPv4,改造费劲 |
| frp这类穿透组件 | 有一台公网服务器 | 部署轻量,灵活,协议无关 | 需要维护转发节点和配置 |
frp 的优势在于它的“普适性”。无论内网是公司出口、家庭宽带还是云上VPC,只要内网机器能主动访问公网的一台服务器,就能把服务映射出去。它不关心业务协议是 SSH、HTTP 还是自定义TCP,因为核心就是一条双向数据通道。
1.3 FRP的两条通道:一个关键理解
理解 frp 一定要抓住两个词:控制通道、数据通道。
frp 客户端启动后会主动连接 frp 服务端,建立一条长期存活的控制通道。这条通道上跑的是握手、心跳、配置同步等控制信息。frp 客户端把自己要暴露的内网服务注册到服务端:“我这边有一个 SSH 服务在 22 端口,请在你的公网机器的 6000 端口上帮我监听。”服务端收到请求后,如果端口未被占用且配置允许,就会真的去监听这个端口。
当用户访问公网服务器的 6000 端口时,服务端会把这次访问转交给对应的客户端。客户端拿到请求后,再把它转给内网的本机服务。整个过程有两段转发:用户到公网服务器是一段,公网服务器到内网机器是另一段。frp 服务端在这里只负责“认路”,实际业务数据它并不关心,也不解析。
这个“客户端主动连入”的设计非常关键,它意味着内网机器不需要公网IP、不需要路由器端口映射,只靠一条出站连接就能把服务暴露出去。这也是为什么 frp 在 NAT 后面、动态IP环境下都能稳定做工的原因。
2. 单节点FRP的完整链路与核心配置拆解
2.1 角色分工与端口规划
先看单个节点的完整拓扑,这是后面高可用方案的基础。
服务端(frps)部署在有公网IP的机器上,负责监听客户端连接、分配端口、转发用户流量。客户端(frpc)部署在内网机器上,负责主动连接服务端、接收转发指令、访问本机服务。两者之间必须能通过服务端的 IP 和端口建立连接,这是唯一前提。
我在单节点演示中通常这样规划端口:
| 角色 | 端口 | 用途 |
|---|---|---|
| frps | 7000 | 主监听端口,frpc通过它连入 |
| frps | 7500 | 内置Dashboard监控界面 |
| frps | 6000-6100 | 允许客户端申请的远程端口范围 |
| frpc | 无固定端口 | 只做主动出站连接,内网无需开入站 |
这里有三个细节要记清楚:第一,7000 是 frps 和 frpc 之间的“见面端口”,和用户访问服务用的端口是两码事;第二,用户访问的是 frps 上动态监听的端口,比如 6000 映射内网的 22 端口;第三,frps 的配置里必须声明允许客户端使用哪些端口范围,否则客户端申请端口会被拒绝。
2.2 frps服务端最小配置与字段含义
以 frp v0.52 之后的版本为例,配置文件是 TOML 格式。老的 INI 配置也还能见,但我强烈建议新部署直接用 TOML,社区维护重心已经全部移到新版上了。
服务端最小的配置是这样的:
bindAddr = "0.0.0.0" bindPort = 7000 auth.method = "token" auth.token = "一个足够长的随机字符串" webServer.addr = "127.0.0.1" webServer.port = 7500 webServer.user = "admin" webServer.password = "一个足够强的密码" transport.heartbeatInterval = 30 transport.heartbeatTimeout = 90 allowPorts = [ { start = 6000, end = 6100 } ]逐行说重点:
bindAddr和bindPort:frps 监听哪个地址和端口。0.0.0.0 表示所有网卡都监听,如果只想让特定网段访问,可以改成具体IP。auth.method和auth.token:客户端连接时必须携带相同的 token,相当于共享密钥。这是第一条防线,必须设置,不要用默认值。webServer:Dashboard 配置。生产环境我强烈建议只监听 127.0.0.1,不要直接暴露到公网。你想远程查看时,用 SSH 转发通道连回来看就行,比裸奔安全得多。transport.heartbeatInterval和transport.heartbeatTimeout:心跳间隔和超时时间。默认值其实够用,NAT 环境如果经常掉线,可以适当缩短心跳间隔,但不要低于 10 秒,否则服务器端会收到大量无意义的心跳包。allowPorts:明确告诉 frps 允许客户端占用哪些远程端口。不写这个字段时 frps 会用系统空闲端口,但生产环境必须收口,防止客户端乱申请端口,这是安全策略的一部分。
2.3 frpc客户端最小配置与字段含义
客户端配置相比服务端略复杂一些,因为每个映射规则都要单独声明:
serverAddr = "203.0.113.10" serverPort = 7000 auth.method = "token" auth.token = "与frps相同的随机字符串" loginFailExit = false [[proxies]] name = "ssh-dev" type = "tcp" localIP = "192.168.1.10" localPort = 22 remotePort = 6000 [[proxies]] name = "web-demo" type = "http" localIP = "192.168.1.20" localPort = 8080 customDomains = ["demo.example.com"]客户端的关键字段:
serverAddr和serverPort:frps 的地址和端口。这里写的是示例地址,实际部署时换成你自己的公网服务器IP。loginFailExit:登录失败时是否退出进程。生产环境必须设成 false,不然 frps 短暂不可用,frpc 第一次重连失败就自己退出了,后面不会自动恢复。这个字段是很多人忽略的坑。[[proxies]]:一个映射规则就是一条。type字段支持 tcp、udp、http、https 等。remotePort是 frps 上对外开放的端口,用户访问的就是这个端口。customDomains配合 HTTP 类型的映射使用,需要把域名解析到 frps 所在机器。
我在真实环境里,会把规则按服务用途分开命名,比如 ssh-dev、rdp-op、mysql-test。命名写清楚了,后期维护时看日志能一眼定位是哪条通道出了问题。
2.4 从公网到内网的一次完整请求
现在把链路串起来。
假设你在家,想 SSH 到公司内网的一台开发机。你在客户端执行:
ssh user@203.0.113.10 -p 6000这条连接到达的是 frps 监听的 6000 端口。frps 查了一下自己的映射表,发现 6000 端口对应的规则是“ssh-dev”,负责这个规则的是某个 frpc 客户端。于是 frps 把这条连接的流量原封不动地丢给 frpc。frpc 收到后,再把数据转发到内网的 192.168.1.10 的 22 端口。开发机的 SSH 服务看到的就是一条来自内网客户端的正常连接,它不需要知道外面发生了什么。
整个过程有一个很直观的类比:frp 像是一个“传话员”,你在大楼外喊一嗓子,传话员跑进大楼里找到要找的人,再把对方的话原封不动带出来。业务双方并不直接对话,但沟通效果和直接对话完全一样。
frps 对业务数据不感知,所以无论跑的是 SSH、HTTP 还是数据库协议,都能转发。这也意味着,任何加密流量你都可以放心地走 frp,传输安全性由业务协议自己保证,frp 本身不需要额外解密。
3. 高可用集群拓扑设计:从单点到双活的关键决策
3.1 单节点到底会怎么挂
单节点的 frps 跑起来很容易,但生产环境里它的脆弱程度往往被低估。
我见过几个典型事故:frps 进程突然退出,重启脚本没搭好,结果所有远程通道全断;机器磁盘写满,日志文件把根分区占满,frps 无法工作;云主机网络波动,frpc 连不上服务端,反复重试;机房维护,整台机器直接关机。任何一类故障发生,哪怕只有几分钟,业务侧就会开始抱怨“怎么又连不上了”。
单节点最大的问题不是“会挂”,而是它挂了之后,你没有任何备选路径。所有 frpc 都指向一台服务器,等于把所有远程通道的命脉都押在一点上。高可用的目标很简单:任何一台节点挂掉,流量自动切到另一台,不需要人半夜爬起来改 DNS 或者改 frpc 配置。
3.2 高可用要解决的四个问题
搭高可用集群之前,先明确要解决什么:
- 入口冗余:frpc 永远有一个固定入口可以连,不会因为某台节点宕机而失联。
- 通道状态恢复:frps 故障后,frpc 能自动重连,并且在新节点上重新建立所有映射。
- 配置一致:多台 frps 的配置必须保持一致,否则客户端切到另一台后会因为规则缺失或凭证不对而失败。
- 切换自动且快速:故障发生时,从检测到恢复的间隔越短越好,最好在秒级。
这四个问题里,第 3 点往往最容易被忽略。很多人搭了双节点,结果节点之间配置不同步,切换过去后一堆规则失效,等于白搭。
3.3 我最终选型的双活结构
我用的方案是“双 frps + 双 HAProxy + keepalived 虚拟 IP + 配置单向同步”。结构上看是这样的:
- 两台入口节点,每台都部署 frps 和 HAProxy。
- frpc 只连接一个虚拟 IP(VIP),不直接关心后端是哪台节点。
- keepalived 负责让 VIP 在两台节点之间漂移,保证总有一个 HAProxy 在 VIP 上工作。
- HAProxy 做四层 TCP 分发,把 frpc 的连接转发到本机或对端的 frps。
- 配置以其中一台节点为源,通过 rsync 单向同步到另一台。
做一个文字版的数据流拆解:
frpc -> VIP:7000 -> HAProxy(主节点) -> frps(任一健康节点) -> 内网服务关键设计点在于分层容错:frps 故障时,HAProxy 的健康检查会把它标记为不可用,新连接自动发给另一台 frps,VIP 不需要漂移;如果整台节点断电,keepalived 的脚本检测失败,VIP 漂移到备用节点,备用节点的 HAProxy 接管 frpc 连接。两层各管各的,不会出现“frps 一挂就整体切换”的震荡。
3.4 为什么不建议“客户端写死多节点列表”
有人会问:把 frpc 里同时配置两个 frps 地址,第一个连不上就连第二个,不是更简单吗?
这个思路表面可行,但实际维护很麻烦。第一,frpc 本身没有真正意义上的多服务端自动切换机制,你要在客户端外面再包一层脚本做探测和切换,这又引入新的故障点。第二,如果两台 frps 各自监听了相同的对外端口(比如都占用了 6000),客户端切到另一台时会发现端口已经被占用,因为这是另一套独立的监听,而不是同一套规则的接管。第三,客户端侧的切换逻辑在不同版本、不同场景下的行为差异很大,调试成本高。
用 VIP 加 HAProxy 的方式,frpc 永远只看到一个入口,切换发生在服务端和网络层,客户端不感知。所有映射规则在两端节点上保持一致,无论落到哪台 frps 上都能正常建通道。这是在可控性和复杂度之间最平衡的做法。
4. 双节点双活落地:配置模板与完整实操
4.1 环境规划与端口矩阵
先在表格里把两台节点的规划写清楚。以下地址均为实验环境示例,实际按你自己的网络调整。
| 节点 | 物理IP | 角色 | 说明 |
|---|---|---|---|
| 节点A | 203.0.113.11 | frps + HAProxy + keepalived | 配置源节点,优先充当VIP持有者 |
| 节点B | 203.0.113.12 | frps + HAProxy + keepalived | 备用节点,配置自动同步目标 |
| VIP | 203.0.113.100 | 虚拟IP | frpc 的唯一连接入口 |
端口分工如下:
| 端口 | 监听位置 | 用途 |
|---|---|---|
| 7000 | HAProxy | 对外统一入口,frpc 连接它 |
| 7001 | frps | frps 与 HAProxy 之间的内部转发端口 |
| 7500 | frps | Dashboard,只监听 127.0.0.1 |
| 6000-6100 | frps | 客户端映射出的远程端口范围 |
我把 frps 的主端口设成 7001 而不是 7000,是为了避免 HAProxy 和 frps 在同一台节点上争抢 7000。外部用户的访问语义没变:frpc 仍然连 VIP 的 7000,用户也仍然访问 VIP 上的映射端口。
4.2 部署frps并接入systemd
两台节点上都执行同样的安装步骤。先下载对应平台的二进制包,放到 /usr/local/bin 下,然后创建配置文件目录和配置。
frps 配置放在 /etc/frp/frps.toml:
bindAddr = "0.0.0.0" bindPort = 7001 auth.method = "token" auth.token = "替换成你自己的超长随机字符串" webServer.addr = "127.0.0.1" webServer.port = 7500 webServer.user = "admin" webServer.password = "替换成你自己的强密码" transport.heartbeatInterval = 30 transport.heartbeatTimeout = 90 allowPorts = [ { start = 6000, end = 6100 } ]然后写一个 systemd 服务文件 /etc/systemd/system/frps.service:
[Unit] Description=frp server After=network.target [Service] Type=simple ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml Restart=on-failure RestartSec=5 LimitNOFILE=1048576 [Install] WantedBy=multi-user.target装载并启动:
systemctl daemon-reload systemctl enable --now frps systemctl status frpsLimitNOFILE这里我特意调大了。frp 的每个连接都会占用一个文件描述符,默认 1024 的 limit 根本不够用。这个参数在长连接场景下救过我很多次,不加的话连接数一上来进程就会报 too many open files。
4.3 HAProxy四层分发配置
HAProxy 的作用是把 frpc 对 VIP:7000 的连接转发到 frps。安装 HAProxy 后,在 /etc/haproxy/haproxy.cfg 里写:
global maxconn 50000 log /dev/log local0 defaults mode tcp timeout connect 5s timeout client 120s timeout server 120s log global listen frps_lb bind 0.0.0.0:7000 balance leastconn server frps-local 127.0.0.1:7001 check inter 3s fall 2 rise 2 server frps-remote 203.0.113.12:7001 check inter 3s fall 2 rise 2节点B上的配置把 local 和 remote 的地址对调即可:frps-local 指向 127.0.0.1:7001,frps-remote 指向 203.0.113.11:7001。
几个细节:
mode tcp表示四层转发,frp 属于长连接场景,不需要七层解析。balance leastconn适合长连接均衡,frp 控制通道一旦建立会长期存活,按连接数分发比轮询更合理。check inter 3s fall 2 rise 2是健康检查参数:每 3 秒探测一次,连续 2 次失败标记为不可用,连续 2 次成功恢复可用。这样 frps 单节点故障时,最多 6 秒左右新连接就会全部落到另一台。- 健康检查只做 TCP 连接探测,不需要额外选项。frps 收到连接后如果收不到业务握手数据,会在超时后清理,这不影响正常通道。
有些人习惯加option tcp-check并自定义探测内容,但在 frp 这种自定义协议场景下反而容易误判。TCP 端口通了,进程就是活的,业务是否正常应该由业务层去监控,不需要 HAProxy 替你做。
启动并确认 HAProxy 正常监听 7000:
systemctl enable --now haproxy ss -lnt | grep 70004.4 keepalived虚拟IP与检查脚本
keepalived 负责维护 VIP 在两台节点之间的漂移。这是整套高可用架构的“大脑”。配置不多,但细节必须抠清楚。
节点A上的 keepalived 配置 /etc/keepalived/keepalived.conf:
global_defs { router_id FRP_HA_A enable_script_security } vrrp_script chk_haproxy { script "/usr/local/bin/check_haproxy.sh" interval 3 fall 2 rise 2 } vrrp_instance FRP_VIP { state BACKUP interface eth0 virtual_router_id 51 priority 100 nopreempt unicast_src_ip 203.0.113.11 unicast_peer { 203.0.113.12 } advert_int 1 authentication { auth_type PASS auth_pass "随机字符串" } virtual_ipaddress { 203.0.113.100/24 dev eth0 } track_script { chk_haproxy } }节点B的配置和节点A几乎一样,只改三处:router_id改成 FRP_HA_B,unicast_src_ip改成 203.0.113.12,unicast_peer改成 203.0.113.11,priority改成 90。
这段配置里有几个容易被忽略的坑:
- 两台节点的
state我都写了 BACKUP,并且加上nopreempt。如果一台写 MASTER 一台写 BACKUP,故障恢复后 MASTER 节点会强制抢回 VIP,这在某些场景会导致不必要的来回切换。nopreempt可以避免这种抢占震荡,让当前持有 VIP 的节点一直服务到它真正故障为止。 - 云环境里 VRPP 组播经常被安全组拦掉,所以这里用
unicast_src_ip加unicast_peer走单播,两台节点之间直接通信,稳得多。 authentication里的auth_pass长度不要超过 8 位,这是 keepalived 的协议限制,超出部分会被静默截断,而且必须保证两台节点一致。
健康检查脚本 /usr/local/bin/check_haproxy.sh 的内容:
#!/bin/bash if [ $(ss -lnt | grep -c ':7000 ') -lt 1 ]; then exit 1 fi exit 0这个脚本只检测 HAProxy 是否活着,特意不检测 frps。前面说过,frps 的故障由 HAProxy 的健康检查兜底,不需要触发 VIP 漂移。如果这里加上 frps 检测,会导致 frps 一挂就切 VIP,HAProxy 又把连接导回原节点,来回切换把日志刷爆。
给脚本加执行权限,然后启动 keepalived:
chmod +x /usr/local/bin/check_haproxy.sh systemctl enable --now keepalived启动后看一眼 VIP 是否绑定成功:
ip addr show | grep 203.0.113.100正常情况下,优先级高的节点A会持有 VIP。停掉节点A的 HAProxy,6 秒左右 VIP 会漂移到节点B。
4.5 配置同步与frpc连接VIP
两台 frps 的配置会随着时间变化,比如新增映射规则、修改 token、调整端口范围。如果不做同步,节点B就成了一台“配置过期”的备用节点,切换过去后发现没有对应的规则,通道建立自然失败。
我采用 rsync 单向同步,以节点A为配置源。在节点A上写一个同步脚本 /usr/local/bin/sync-frp-config.sh:
#!/bin/bash rsync -avz --delete /etc/frp/ 203.0.113.12:/etc/frp/ ssh 203.0.113.12 "systemctl restart frps"加入 crontab,每 5 分钟同步一次:
*/5 * * * * /usr/local/bin/sync-frp-config.sh > /dev/null 2>&1注意两点。第一,--delete参数很重要,源端删掉的配置文件也会在目标端删除,否则会残留旧规则。第二,同步完成后重启 frps 会让正在连接它的客户端断一次。如果你觉得 5 分钟一次太频繁,可以降低频率或者改成手动执行刷配置;连接数少的时候,重启带来的影响其实可感知但可控。
如果两台节点之间网络不允许直接互访,可以改用 git 仓库做配置管理,节点A提交后节点B拉取,再触发重启。方式不同,思路一样:配置文件必须一致。
frpc 端的配置反而不需要高可用。它只需要指向 VIP:
serverAddr = "203.0.113.100" serverPort = 7000 auth.method = "token" auth.token = "与frps相同的随机字符串" loginFailExit = false [[proxies]] name = "ssh-dev" type = "tcp" localIP = "192.168.1.10" localPort = 22 remotePort = 6000客户端永远只认 VIP,服务端切换对它完全透明。这也是这套架构最让人省心的地方。
5. 故障切换实测与三个高频坑
5.1 故障注入实测:暂停frps与重启节点
配置都上了之后,我做过几轮故障注入,这里把实测结果分享出来。
第一轮:节点A上停掉 frps 进程。
systemctl stop frpsHAProxy 的健康检查在 3 秒后第一次探测失败,第二个 3 秒后再次失败,随后把节点A的 frps 标记为不可用。新连接全部转向节点B,整个切换过程大约 6 到 8 秒。已有连接全部断开,frpc 检测到控制通道断开后开始重连 VIP。HAProxy 此时已经把连接导向节点B,frpc 在节点B上重新登录并重建所有映射规则。整个过程从用户角度看,就是一次远程连接断开,再重新连接即可恢复。
第二轮:直接模拟整机断电,把节点A的对外网络断掉。
这次是 keepalived 在起作用。节点A的 HAProxy 探测不到,检查脚本连续失败,VRRP 宣告节点A失联。节点B在几个通告周期后接管 VIP,耗时大概 5 到 10 秒。frpc 同样重连后恢复。
有一个现象需要提前向业务侧说明:故障切换会让所有正在使用中的长连接中断一次,然后自动恢复。SSH、数据库客户端这类应用表现为“断线后重连”,HTTP 请求可能超时一两次。这不是配置有问题,而是原有的数据通道确实建立在旧节点上,节点切换后旧通道无法跟随漂移。如果想要保持会话不中断,要么在业务层做会话保持,要么对这个场景降低切换频率,提前规避。
5.2 坑一:token不一致导致重连后鉴权失败
有一次我调整了 token,在节点A上改了配置并重启,忘记了节点B的配置还没同步。表面看起来一切正常,因为 frpc 连的是 VIP,而 VIP 还挂在节点A上。等节点A真正故障、VIP 漂移到节点B时,所有 frpc 连上来都被拒。日志里大量报鉴权失败,朋友问我“为什么切过去了还是连不上”。
排查过程是这样的:先看 frpc 的日志,确认它确实在反复重试连接 VIP,连接本身是通的,登录被拒。再看节点B的 frps 日志,里面有明显的认证失败记录。最后对比两台节点的配置文件,才发现 token 不一致。
这个坑的根因在于,配置同步只覆盖了规则部分,凭证信息没有跟着同步。现在我的同步脚本会同步整个 /etc/frp 目录,token、端口范围、DashBoard 密码这些全部包含在内。改任何配置都从配置源节点开始,保证改动能传达到所有节点。
5.3 坑二:keepalived抢占模式导致反复切换
早期我在配置里把节点A写成 MASTER,节点B写成 BACKUP,还加了preempt。结果就是每次节点A从故障中恢复,VIP 都会被抢回来,客户端跟着断一次。如果节点A的网络时好时坏,VIP 就会在几分钟内来回漂移好几次,业务侧看到的是一场“持续的断连风暴”。
这不是 keepalived 本身的 bug,而是我的配置策略不对。后来我把两台都改成 BACKUP 加nopreempt,用 priority 决定初始状态:节点A 100,节点B 90。没有抢占,就没有来回切。当前持有 VIP 的节点会一直服务到它自身检查失败,切换只在真正需要的时候发生。
这个经验同样适用于其他 VRRP 场景。抢占模式适合主备强约束的架构,但如果你追求的是双活和最小抖动,nopreempt更合适。
5.4 坑三:健康检查过于灵敏,切换比预期频繁
有一次我发现集群在没有任何人工操作的情况下频繁切换,排查后发现是健康检查参数太激进。当时我把 HAProxy 的检查间隔设成了 1 秒,fall 次数设成 1。结果一次正常的网络抖动,或者 frps 短暂阻塞,检查就判定失败,触发切换。切换本身其实没毛病,但频繁的切换会让所有长连接反复断开,造成比故障本身更差的体验。
健康检查的参数应该根据网络质量来定。云主机之间如果走的内网,网络质量好,间隔 3 秒、fall 2 是合适的;如果是跨地域的公网链路,间隔可以放宽到 5 秒,fail 次数放到 3。原则是:能识别出真实故障,又不会因为瞬时抖动而误判。
5.5 心跳参数的调优边界
frp 的心跳参数也会影响可用性。heartbeatInterval是客户端发心跳的间隔,heartbeatTimeout是服务端容忍多久没收到心跳就判定连接失效。默认 30/90 是一个平衡值,一般不用动。
有几个特殊情况需要调:如果内网出口防火墙对空闲连接不友好,会在几分钟内清理没有流量的 TCP 连接,这时可以把心跳间隔缩短到 10 秒;如果你在内网机器的 frpc 上还叠加了网络代理或负载均衡,链路变长后,超时时间也要相应放大,给中间设备留出处理余地。
但心跳间隔不是越短越好。太短会产生大量空包,占用带宽和文件描述符;太长会导致链路断了很久才被发现,恢复时间被拉长。我的习惯是:普通公网环境保持 30/90,NAT 严格的办公网络在出问题时先调到 15/45,观察一周没有掉线记录再保留这个值。
6. 生产环境安全加固与性能边界
6.1 认证、TLS与Dashboard保护
frp 的认证机制不复杂,但很多人部署时用了默认token,等于裸奔。生产环境至少要满足三条:
- token 使用随机生成的超长字符串,长度不低于 32 位,且只在配置源节点上维护,通过同步分发到其他节点和客户端。
- 传输层启用 TLS。frps 配置里加
transport.tls.force = true,frpc 配置里加transport.tls.enable = true,让客户端与服务端之间的控制通道、数据通道都走加密。虽然 frp 默认不验证证书,但至少可以防住链路被监听。 - Dashboard 必须绑定 127.0.0.1,不要开放公网访问。我见过有人图方便把 7500 端口直接暴露出去,结果弱口令被扫描器撞开,整个 frps 的仪表盘被人看光。
6.2 端口收口与访问控制
frp 最容易被滥用的一点是:客户端可以申请把内网服务映射到远程端口。如果你不限制,任何一个拥有 token 的人都能把你的公网机器变成内网跳板。
因此我强烈建议做好三层收口:
- 在 frps 的
allowPorts里只开放业务实际需要的端口范围,比如 6000-6100。 - 在 frps 的
bindAddr上绑定内网网卡或物理IP,不要一上来就 0.0.0.0 暴露所有网卡。 - 在云安全组层面,只放行 7000、6000-6100 等必要的入站端口到公网,7001 只允许两台节点之间的网段互访。
另外,frpc 侧不要把 remotePort 设置成不必要的范围。很多场景下内网服务只需要给特定几个人用,暴露的端口越少越好。内网服务本身该有的认证也必须有。frp 只是通道,不负责业务鉴权。如果你把没有密码保护的 HTTP 服务直接映射出来,任何人扫到端口就能访问。
6.3 监控告警与日志留存
高可用架构跑起来之后,日常运维的重点就变成了监控。我的做法比较朴素,三样东西:
- frps 的 Dashboard:看一眼在线客户端数量、连接状态。但因为 Dashboard 只绑本机,我需要通过 SSH 转发过来查看。
- 日志监控:frps 和 frpc 的日志里如果频繁出现重连、鉴权失败、端口冲突,说明有问题。把这些关键字提取出来,接上告警通道。
- HAProxy 状态页:HAProxy 自带统计页面,可以看到每台 frps 的健康状态和当前连接数,是我判断是否需要人工介入的第一依据。
日志要定期轮转,frp 配置里可以设置log.to指向一个文件。生产环境我建议至少保留 30 天的日志,方便追溯“某个时间点为什么断了一次”。
6.4 性能瓶颈与“该用专线就用专线”
最后说一个容易被忽略的问题:frp 不是万能的传输加速器。
frp 本质上是一段用户态程序的流量转发,吞吐能力受限于机器网卡、系统中断处理、进程文件描述符上限和网络链路质量。中小流量的远程运维、网页访问、API 调试,跑起来完全没问题。但如果要传输大量视频流、数据库全量备份、大文件拷贝,frp 会成为转发瓶颈,此时你真正需要的是一条物理专线或者更高带宽的网络方案。
高可用集群引入了 HAProxy 这一跳,性能会有轻微损耗,但这层损耗换来的是入口冗余和自动切换能力,对大多数远程访问场景是值得的。真到了对带宽和延迟极其敏感的关键业务,可以考虑让 frps 同时承载业务入口,HAProxy 只负责控制通道的高可用,或者干脆把专线接入作为主路径,frp 集群作为备份路径。
我在实际使用中的一个体会是,这类穿透方案最适合的定位是“应急和远程运维”,而不是长期承载高并发业务流量。把不同场景放到合适的通道里,整个集群才能长期健康地运转。
整套方案跑下来,我最满意的改进是把“人工改配置”这件事彻底去掉了。frpc 永远只连 VIP,通道规则变更只需要在配置源节点上改一次,同步脚本会自动分发并重启备节点。故障切换完全交给 keepalived 和 HAProxy,不需要人半夜爬起来改地址。
最后分享一个长期养成的习惯:任何 frp 版本升级之前,先在两台节点上看一下新版配置字段的变更说明,历史上有过几次配置格式不兼容,直接用新版本覆盖旧配置会起不来。升级时我会先在节点B上做验证,确认配置加载正常后再同步到节点A,避免一次性把两个节点都搞挂。这套流程看起来多了一步,但在生产环境里,多出来的这一步往往就是避免事故的关键。