场景:调休一天,开发机连不上
10月调休在家,笔记本上只有公司内网才能跑的服务。拉出公司发的Windows笔记本连VPN,Security Service检查完网络策略,结果只能走公司特定的SSL VPN客户端,且只允许443端口出站。
我自己的Mac上装了WireGuard,但公司防火墙把UDP 51820封得死死的,连上3秒必断。想SSH到开发机写代码,跑不了一点。工作日早上10点,我需要突破这个限制。
方案对比:WireGuard、OpenVPN、frp
| 方案 | 协议 | 延迟 | 公司防火墙 | 配置复杂度 | 结论 |
|---|---|---|---|---|---|
| WireGuard | UDP/51820 | 最低(内核态) | UDP被封锁,无法使用 | 低 | 推荐,但公司防火墙不放过 |
| OpenVPN | TCP/UDP | 中间(用户态) | 标准配置方案可走443,但仍需服务端 | 高 | 配置略重,证书管理麻烦 |
| frp | TCP | 比WireGuard高约10%(用户态多一次拷贝) | 服务端443端口可轻松穿透 | 低 | 首选,单端口复用,多域名分流 |
最终选定 frp v0.57.0(frp 最新稳定版,Go语言编写,单二进制,无需额外运行环境)。它本质是个 TCP 中继:公司出口的 HTTPS 流量本身就建立 TCP 连接,所以 frp 里配置服务端监听 443 端口,客户端主动连过来,再映射到开发机的 22 端口,流量就是一条长 TCP,不会被动断掉。
frp 核心原理(简)
frp 分为 frps(服务端)和 frpc(客户端)。frpc 主动向 frps 建立 TCP 控制连接,frps 收到来自公网用户的 TCP 连接请求后,通过控制连接告知 frpc 建立一条数据通道。数据转发路径为:
「外部用户 → frps(云服务器:443) → TCP隧道 → frpc(公司开发机:22)」
frp 支持 TCP/UDP/HTTP/HTTPS/STCP/XTCP 等多种代理类型。远程开发只用到 TCP 与 STCP:TCP 适合直接 SSH,STCP 适合需要访问侧也能访问、但不想暴露过多端口时。
部署环境与版本
# 云服务器(frps)
# 腾讯云 2C4G Debian 12(内核 6.1),公网IP 1.2.3.4
# 公司开发机(frpc)
# Ubuntu 22.04,内网IP 192.168.1.100,22端口可用
# frp v0.57.0
# 客户端支持平台:linux_amd64 / linux_arm64 / darwin_amd64
1. 安装 frp 二进制
# 在云服务器 (frps) 与开发机 (frpc) 上分别下载
mkdir -p /usr/local/frp
cd /usr/local/frp
wget https://github.com/fatedier/frp/releases/download/v0.57.0/frp_0.57.0_linux_amd64.tar.gz
tar -zxvf frp_0.57.0_linux_amd64.tar.gz
cp frp_0.57.0_linux_amd64/frps /usr/local/bin/ # 服务端
cp frp_0.57.0_linux_amd64/frpc /usr/local/bin/ # 客户端
chmod +x /usr/local/bin/frps /usr/local/bin/frpc
frp v0.57.0 采用 TOML 格式配置,不再使用旧版 INI。
2. frps.toml 配置(云服务器)
# /usr/local/frp/frps.toml
bindPort = 7000 # frpc 控制连接端口
bindAddr = "0.0.0.0"
kcpBindPort = 7000 # KCP可选,UDP被墙则无效,此处仅为兼容
quicBindPort = 7001 # QUIC 监听,若UDP被墙无需开启,先注释掉
# quicKeepalivePeriod = 10
# quicMaxIdleTimeout = 30
# 服务端鉴权配置:客户端要携带相同token
auth.token = "你的超强随机token_至少32位"
# 公网用户访问 frps 的端口,用于 SSH,但 frp 本身支持端口复用
# 我们可以将 `vhostPort` 设为443,然后仅启用一个 TCP 代理
# 同时允许通过 HTTP 的 Host 头做域名路由
allowPorts = [{ start = 10022, end = 10022 }] # 只允许本服务映射 10022 端口出去
# 日志
log.to = "/var/log/frps.log"
log.level = "info"
log.maxDays = 7
# 管理面板
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "你的面板密码"
# 限制每用户的最高带宽(默认0不限制)
# transport.maxPoolCount = 5
将公网 443 端口与 frps 的 TCP 代理直接关联,可以实现单端口穿透。frp 内置的 HTTP 类型 vhost 也可以直接绑定到 80/443,更多场景可参考官方文档。
3. frpc.toml 配置(开发机)
# /usr/local/frp/frpc.toml
serverAddr = "1.2.3.4"
serverPort = 7000
auth.token = "你的超强随机token_至少32位"
# 日志
log.to = "/var/log/frpc.log"
log.level = "info"
log.maxDays = 7
# 远程登录开发机用:TCP 隧道
[[proxies]]
name = "dev-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 10022
# 安全加固:仅允许来源IP(可选)
# frp 在 0.57.0 支持 Proxy 维度插件,但 IP 白名单可以交给 frps 的 allowPorts 与 iptables 一起控制。
# 远程访问开发机上的 MySQL:供本地调试用
[[proxies]]
name = "dev-mysql"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3306
remotePort = 10033
# 远程访问 Redis(本地限流使用,必须关闭所有外部监听)
[[proxies]]
name = "dev-redis"
type = "tcp"
localIP = "127.0.0.1"
localPort = 6379
remotePort = 10064
# 远程开发常见局面:VS Code Remote-SSH 只需要 SSH 即可,无需额外 HTTP
# 如果希望以后暴露给身边同事访问,可以改用 stcp,更安全。
# 这里提供 stcp 模式的极简配置,启用后 frpc 与 frps 之间通信不直接暴露公网端口:
# [[proxies]]
# name = "dev-ssh-stcp"
# type = "stcp"
# secretKey = "对端也需要相同的secretKey"
# localIP = "127.0.0.1"
# localPort = 22
注意:MySQL 与 Redis 不应映射到公网,这里为了展示方便,实际上应该把它们通过 ssh 隧道再转发一次,或用 stcp + 访问端 frpc 实现。
4. systemd 守护服务(服务端和客户端)
# /etc/systemd/system/frps.service —— 云服务器上
[Unit]
Description=frps Service
After=network.target
[Service]
Type=simple
User=nobody
Group=nogroup
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/bin/frps -c /usr/local/frp/frps.toml
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/frpc.service —— 开发机上
[Unit]
Description=frpc Service
After=network.target
[Service]
Type=simple
User=nobody
Group=nogroup
Restart=always
RestartSec=5s
ExecStart=/usr/local/bin/frpc -c /usr/local/frp/frpc.toml
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
# 启动并设置开机自启
systemctl daemon-reload
systemctl start frps
systemctl enable frps
systemctl status frps --no-pager
5. 本地 SSH config 别名
# ~/.ssh/config
Host dev-box
HostName 1.2.3.4
Port 10022
User yourname
ServerAliveInterval 30
ServerAliveCountMax 3
TCPKeepAlive yes
ProxyCommand none
用 ssh dev-box 即可直接登录到公司开发机。
性能实测:真实数据对比
测试环境:frps 位于腾讯云上海(2C4G),frpc 所在公司网络在深圳,物理RTT约38ms。测试时间:工作日白天。
1. 延迟对比
# Ping 外网(控制组)
ping -c 50 1.2.3.4 # 平均 38ms
# SSH 登录后测试交互输入延迟(交互式)
time ssh dev-box 'echo start; sleep 1; echo end'
# 实际感受:SSH回车到返回结果 ≈ 2×38ms + 用户态转发 ≈ 82ms
# 即多跳了一层frp中转,延迟约增加 5~12ms(与网络环境有关)
2. 吞吐:iperf3 测试 TCP 隧道
# 开发机执行(frpc侧)
iperf3 -s
# 本地电脑执行
iperf3 -c 1.2.3.4 -p 10022 -t 30
# 结果:带宽约 41.7 Mbps,相比直连公网服务器(约 93 Mbps)下降了 55%
# 原因:frp 单线程转发的瓶颈 + TCP tunnel 流控。若带宽需求较高,建议在frpc侧配置TCP拥塞控制
# 即 frps.toml 配置 transport.tcpMux = true,且客户端同时开启 tcpMux。
3. SSH 交互响应延迟实测
# 使用 ssh 连接后,输入一个字符,从按下到回显的平均时间:
# 直连(无代理):86ms
# 走 frp(TCP隧道):92ms
# 增加约 6ms,体感差异极不明显,但比WireGuard的4ms慢约2ms
4. 长连接稳定性(24小时)
# 持续 SSH 保持 24 小时,断开次数 0,小流量保活机制下未出现断流。
# 大文件传输 sar 记录:CPU占用 平均 3%,峰值 23%
5. VS Code Remote-SSH 实际体感
- 打开远程项目首次索引:约 25 秒
- 输入字符延迟:基本无感(低于 200ms 后人类无法感知差异)
- 远程终端打卡:响应 < 50ms
- 受网络带宽影响,代码提示 IntelliSense 偶有延迟约 200ms,总体可用。
结论:frp 适合绝大多数远程开发场景,尤其是端口受限、UDP被墙的网络。延迟增加约 5~12ms,代价可忽略;但带宽受限于单线程,不适合大数据迁移。
避坑指南(全是实战踩出来的)
- 不要在 frps 上直接开放过多端口。 用
allowPorts限制白名单,防止开发机被扫。frp 在 v0.57.0 中如果配置的remotePort不在 allowPorts 范围内,服务端会拒绝启动。 - 服务端和客户端版本必须一致。 否则会报
frpc connect to server failed,日志显示 sign 错误。直接用相同版本二进制最省心。 - token 不要用弱口令。 防火墙只封了 UDP,并没有封全网,公网扫描器频繁探测 443 端口。建议随机生成32位以上 token。
- systemd 里 User=nobody 时日志目录不能被 nobody 写入。 如果日志路径配了 /var/log/frps.log,但/var/log目录权限正常(多数是755),nobody 无法创建文件,启动即失败。此时改为 User=root 或提前创建日志文件并 chown nobody。
- 不要为了“展示”把 MySQL/Redis 映射到公网。 如果你需要本地调试,请先 SSH 隧道:
ssh -L 3306:127.0.0.1:3306 dev-box,而不是把 MySQL 端口通过 frp 暴露给公网。MySQL 本身不带加密,后果你懂。 - UDP 被墙根本不用配 KCP 或 QUIC。 你连 UDP 都出去,KCP 也是废的。如果公司允许 UDP,那么直接用 WireGuard 更好,frp 的 KCP 模式性能不如直接 WireGuard。
- SSH 连上后粘滞/卡死? 大概率是 MTU 问题。frp 隧道是 TCP over TCP,注意调整
transport.tcpMux = true以及transport.tcpMuxKeepaliveInterval = 60。不要过度修改 MSS。 - 使用 VS Code Remote-SSH 时,如果出现 500 Oops,可能是 frps 上设置了日志重定向到 /dev/null 导致 frps 崩溃。 把日志改为文件后正常。
- frp 的
log.level设置为 debug 可能造成磁盘 IO 暴涨,长时间运行后服务器卡死。 生产环境必须 info。 - 若要映射多个服务,不建议一端口一映射。 用 HTTP 类型 + 域名转发更高效,但开发环境下 SSH 需要固定的 TCP 端口。所以把你的 SSH 映射在
10022,其他服务用 ssh -L 隧道。 - 被公司防火墙的 DPI(深度包检测)识别出 SSH 特征? 目前 frp 流量是明文,公司如果分析 TCP 载荷会发现 SSH 协议特征。这并不违反合规,但如果你们公司有严格的安全策略,建议不要用 frp 绕过防火墙做未经授权的远程接入,先走审批流程。
扩展:更安全且更完善的远程开发配置
如果有权限,建议使用 stcp 模式,仅限指定 frpc 之间可以连接,不再暴露公网端口。需要额外在两台机器上分别配置 frpc:
# 开发机上的 frpc 配置 (stcp 模式)
# [[proxies]]
# name = "secure-ssh"
# type = "stcp"
# secretKey = "sameKeyCanBeAnyString"
# localIP = "127.0.0.1"
# localPort = 22
# 访问机上的 frpc 配置 (连接 stcp 代理)
# [[proxies]]
# name = "secure-ssh-visitor"
# type = "stcp"
# role = "visitor"
# serverName = "secure-ssh"
# secretKey = "sameKeyCanBeAnyString"
# bindAddr = "127.0.0.1"
# bindPort = 6000
# 然后 SSH 连接 localhost:6000 即可。
stcp 模式下不监听公网端口,安全性极高,但只能在能访问到 frps 的机器间使用,适合多开发机内网互通。
总结
frp 快速解决了我在受限网络下远程开发的问题。核心是:服务端一个配置文件,客户端一个配置文件,systemd 做守护,非常稳定。对于公司有严格网络策略的场景,务必提前获得授权。