零、为什么会有这篇东西
2025 年,AI 浪潮席卷全球。所有人都在跑大模型、搞 AI 应用。
我看了看角落里那台 Mac Mini 2012——i5 双核,16GB 内存,机械硬盘,系统还停留在 macOS 版本老到连 Chrome 都不给更新了。
"这玩意儿还能干点啥?"
答案是:装 Ubuntu 22.04,当一台 7x24 小时的本地 AI 服务器。
于是我在上面跑起了 opencode 的 Web 服务。问题来了——这些服务都在内网,外面访问不了。
frp 就是干这个的。一条隧道,把内网服务暴露到公网 ECS 上。
一、拓扑
┌─ 内网(Mac Mini 2012)─────────────┐
│ 系统: Ubuntu 22.04 │
│ frpc 0.69.1 │
│ └── :4096 → ECS:4097 (opencode服务)│
│ 认证: token │
└──────────────┬───────────────────────┘
│ frp 隧道 (TCP)
▼
┌─ ECS ───────────────────────────────┐
│ frps │
│ bindPort = 7000 │
│ └── :4097 (来自 frp 隧道) │
│ 防火墙: 7000 通, 4097 被拦截中... │
└───────────────────────────────────────┘
二、frp 是啥
frp 全称 fast reverse proxy,一个 Go 写的内网穿透工具。
工作原理简单粗暴:
- 你在内网机器上跑 frpc(客户端),在公网服务器上跑 frps(服务端)
- frpc 主动连接 frps,建立一条长连接
- 外部用户访问
ECS公网IP:端口→ frps 收到 → 通过隧道转发给 frpc → 到本机服务
对你来说:用户访问 ECS 的 4097 端口,等于访问你 Mac Mini 上的 4096 端口。
三、frps 安装(ECS 服务端)
ECS = 你的公网服务器,负责接收 frpc 的连接并向外暴露端口。
3.1 下载 frp
frp 是单二进制文件,下载解压就行,没有依赖,没有 node_modules。
# 挑个版本,0.69.1 是我用的
wget https://github.com/fatedier/frp/releases/download/v0.69.1/frp_0.69.1_linux_amd64.tar.gz
tar xzf frp_0.69.1_linux_amd64.tar.gz
sudo cp frp_0.69.1_linux_amd64/frps /usr/local/bin/
sudo chmod +x /usr/local/bin/frps
3.2 配置 frps.toml
bindPort = 7000
auth.method = "token"
auth.token = "你的token"
bindPort = 7000:frpc 通过这个端口注册连接,整个 frp 体系只暴露这一个口子auth.token:客户端用这个 token 认证,相当于钥匙
3.3 systemd 服务
/etc/systemd/system/frps.service:
[Unit]
Description=FRP Server
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
启动:
sudo systemctl daemon-reload
sudo systemctl enable --now frps.service
sudo systemctl status frps.service
确认端口在监听:
ss -tlnp | grep frps
# 应该看到 :7000
token 是在 ECS 上设的,设好后通过安全渠道发给 frpc 配置者。两端不一样会认证失败,不通。
四、frpc 安装(内网机器)
内网机器 = 你的 Mac Mini 2012(Ubuntu 22.04),服务都跑在这上面。
4.1 下载 frp
和 ECS 一样,下载同一版本:
wget https://github.com/fatedier/frp/releases/download/v0.69.1/frp_0.69.1_linux_amd64.tar.gz
tar xzf frp_0.69.1_linux_amd64.tar.gz
sudo mkdir -p /usr/local/frp
sudo cp frp_0.69.1_linux_amd64/frpc /usr/local/frp/
sudo chmod +x /usr/local/frp/frpc
4.2 配置 frpc.toml
/etc/frp/frpc.toml:
serverAddr = "你的ECS公网IP"
serverPort = 7000
auth.method = "token" #它就是'token'这个字符串
auth.token = "你的token"
# ============================================
# 隧道配置:开一条隧道暴露一个端口
# ============================================
# opencode 服务(本机 4096 → ECS:4097)
[[proxies]]
name = "web-4096"
type = "tcp"
localIP = "127.0.0.1"
localPort = 4096
remotePort = 4097
字段说明:
| 字段 | 含义 |
|---|---|
serverAddr | ECS 公网 IP(不是域名,DNS 还没配好之前直接用 IP) |
serverPort | 和 frps 的 bindPort 保持一致,默认 7000 |
auth.token | 和 frps 的 auth.token 一致,一个字都不能差 |
localIP | 本机服务地址,一般 127.0.0.1 |
localPort | 本机要暴露的服务端口 |
remotePort | ECS 上对外监听的端口 |
name | 隧道名称,唯一标识,随便起但别重复 |
逻辑:ECS:remotePort → frp 隧道 → 本机:localPort
4.3 systemd 服务
/etc/systemd/system/frpc.service:
[Unit]
Description=FRP Client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/frp/frpc -c /etc/frp/frpc.toml
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
启动:
sudo systemctl daemon-reload
sudo systemctl enable --now frpc.service
sudo systemctl status frpc.service
4.4 验证 frpc 运行
sudo journalctl -u frpc.service --no-pager -n 20
如果看到以下两行,恭喜,隧道建好了:
[I] [proxy/proxy_manager.go:180] proxy added: [web-4096]
[I] [client/control.go:165] [web-4096] start proxy success
start proxy success 的意思是:frpc 告诉 frps "请在 4097 等我",frps 答了一声 "OK 已就绪"。这只是注册成功,不代表外面能访问(防火墙表示还没点头)。
五、token 怎么来的
token 不是自动生成的,是 ECS 管理员和你对接时约定的暗号:
- ECS 管理员 在
frps.toml里设了一个 token - 你 在
frpc.toml里写同样的值 - frpc 连上来时亮出 token,frps 一比对,对上就放行
现实场景:ECS 管理员丢给你一条微信消息:"token 是 xxx"。你默默粘进配置文件,
systemctl restart frpc,走起。
六、已对接的隧道
| 隧道名 | 本机端口 | ECS 端口 | 用途 |
|---|---|---|---|
web-4096 | 4096 | 4097 | opencode Web 服务(Bun/Elysia HTTP) |
七、端口连通性
从外网实测:
timeout 3 bash -c 'echo > /dev/tcp/ECS公网IP/端口' && echo "通了" || echo "不通"
| ECS 端口 | 状态 | 说明 |
|---|---|---|
| 7000 | ✅ 可达 | frp 控制端口,frpc 长连接正常 |
| 4097 | ❌ 不可达 | 防火墙拦了,流量到不了 frps |
为什么 frpc 日志正常,4097 却不通?
因为 frp 的控制面和数据面是分离的:
- 控制面:frpc → frps 走 7000 端口。注册隧道、心跳、控制指令都跑在这条长连接上。7000 防火墙是开的,所以 frpc 能连上来、注册成功、日志漂亮。
- 数据面:外部用户 → ECS:4097。防火墙在外面就把 4097 的包拦截了,frps 根本收不到,工作连接建不起来。
所以 frpc 日志显示 start proxy success ≠ 外部能访问。控制面通了,数据面入口被墙了。
流量完整路径:
外部用户 → ECS:4097 ✅ 防火墙放行 → frps → 工作连接(走7000) → frpc → 本机:4096
现在缺的就是放行 4097。
需要 ECS 上做的
sudo firewall-cmd --add-port=4097/tcp --permanent
sudo firewall-cmd --reload
然后去云服务商控制台 → 安全组 → 添加入方向规则:
| 方向 | 协议 | 端口 | 授权对象 |
|---|---|---|---|
| 入方向 | TCP | 4097 | 0.0.0.0/0 |
设置完可以看到
八、验证
在ECS里执行:
curl -v http://ECS公网IP:4097
九、日常故障排查
查看 frpc 状态
sudo systemctl status frpc.service
sudo journalctl -u frpc.service --no-pager -n 20
日志正常连不上
八成是 ECS 防火墙/安全组没放行隧道端口。从第三方机器测一下:
timeout 3 bash -c 'echo > /dev/tcp/ECS公网IP/4097' && echo "通了" || echo "不通"
不通就去 ECS 上放防火墙,放完再测。
连接池满
[E] [client/control.go:144] StartWorkConn contains error: work connection pool is full
在 frps.toml 加上:
transport.maxPoolCount = 50
端口在 ECS 上能看到吗
ss -tlnp | grep frp
能看到 LISTEN 4097 说明 frps 成功绑定了端口。不通就是防火墙拦了,和 frp 无关。
十、安全注意事项
- token 别写死在代码里。现实是大家都会写死在配置文件里(我就这么干的),但长远的正确做法是走环境变量或密钥管理服务
- ECS 安全组应限制 7000 端口只允许 frpc 的 IP 访问,别全网暴露。不过 frp 有 token 认证兜底,风险可控
- 隧道只转发流量,不鉴权。如果业务服务本身有敏感数据,务必在服务上加认证,别指望隧道帮你挡人
附录:文件清单
ECS 侧:
/usr/local/bin/frps # frps 二进制
/etc/frp/frps.toml # bindPort=7000, auth.token
/etc/systemd/system/frps.service # systemd 服务
内网 Mac Mini:
/usr/local/frp/frpc # frpc 二进制 (v0.69.1)
/etc/frp/frpc.toml # 客户端配置,隧道定义
/etc/systemd/system/frpc.service # systemd 服务
本机服务端口:
:4096 → opencode Web 服务(Bun/Elysia)