frp 可以让没有公网 IP 的内网机器,通过一台公网服务器对外提供 SSH、Web 等服务。本文用 Compose 部署:公网机跑 frps,内网机跑 frpc,鉴权用 token,管理面板加访问限制。示例版本锁定 v0.61.1(0.52+ 统一使用 TOML 配置)。
服务端与客户端版本必须一致,token 必须一致。
一、服务端 frps(公网服务器)
目录:
/opt/frps/
├── docker-compose.yml
└── frps/
└── frps.toml
docker-compose.yml(host 网络,后续每个代理端口无需逐一发布):
services:
frps:
image: fatedier/frps:v0.61.1
container_name: frps
restart: unless-stopped
network_mode: host
volumes:
- ./frps/frps.toml:/etc/frp/frps.toml:ro
command:
- -c
- /etc/frp/frps.toml
security_opt:
- no-new-privileges:true
mem_limit: 128m
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:7500/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
frps/frps.toml:
# 客户端连接端口
bindPort = 7000
# 管理面板:建议只绑回环,用 SSH 隧道访问
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "替换为强密码"
# token 鉴权(openssl rand -hex 32 生成,64 位)
auth.method = "token"
auth.token = "替换为与客户端一致的64位随机串"
# 如果要按域名转发 HTTP 服务,再打开:
# vhostHTTPPort = 8080
面板确实需要公网访问时,把 addr 改成
0.0.0.0并务必用强密码,或干脆只通过 Nginx 反代 + HTTPS + IP 白名单暴露。
云安全组放行:
- 7000/tcp(frpc 回连)
- 每个代理声明的 remotePort,例如 6000/tcp(SSH 穿透);规划一批就放一段,如 6000-6010
- 7500 不要对公网开放(回环 + 隧道方案下)
启动:
cd /opt/frps
docker compose up -d
docker compose ps
docker compose logs -f
ss -ltnp | grep -E ':7000|:7500'
二、客户端 frpc(内网机器)
内网机同样要装好 Docker。目录:
/opt/frpc/
├── docker-compose.yml
└── frpc/
└── frpc.toml
docker-compose.yml(需要穿透本机 SSH 时用 host 网络最直接):
services:
frpc:
image: fatedier/frpc:v0.61.1
container_name: frpc
restart: unless-stopped
network_mode: host
volumes:
- ./frpc/frpc.toml:/etc/frp/frpc.toml:ro
command:
- -c
- /etc/frp/frpc.toml
security_opt:
- no-new-privileges:true
mem_limit: 64m
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
frpc/frpc.toml(示例:把本机 SSH 暴露到服务端 6000 端口):
serverAddr = "203.0.113.10" # 替换成你的公网服务器 IP(或域名)
serverPort = 7000
auth.method = "token"
auth.token = "替换为与服务端一致的64位随机串"
# 穿透本机 SSH:外网通过 ssh -p 6000 user@203.0.113.10 连入
[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
# 穿透内网另一台机器的 Web 服务(bridge 网络时 localIP 写内网可达地址即可)
# [[proxies]]
# name = "intranet-web"
# type = "tcp"
# localIP = "192.168.1.20"
# localPort = 80
# remotePort = 6001
# 按域名转发 HTTP(需在服务端配置 vhostHTTPPort,并把域名解析到服务器)
# [[proxies]]
# name = "web-by-domain"
# type = "http"
# localIP = "127.0.0.1"
# localPort = 8080
# customDomains = ["app.example.com"]
启动并观察注册是否成功:
cd /opt/frpc
docker compose up -d
docker compose logs -f
# 出现 "start proxy success" 即成功
三、验证
# 在任意外部机器上,经公网服务器跳回内网 SSH
ssh -p 6000 内网用户名@203.0.113.10
# 服务端面板(先在本机建隧道)
ssh -L 7500:127.0.0.1:7500 user@203.0.113.10
# 浏览器开 http://127.0.0.1:7500,用 frps.toml 里的账号密码登录
面板可以看到每个 proxy 的在线状态、进出流量;proxy 显示 offline 时先查客户端日志。
四、加固建议
- token 用
openssl rand -hex 32,两端配置文件chmod 600;token 就是全部准入凭据,泄漏等于任何人能往你的服务器上打洞。 - 服务端安全组只放行 7000 和实际使用的 remotePort 段,面板 7500 不对公网开。
- 可以在
frps.toml加allowPorts限制客户端只允许映射指定端口段:[[allowPorts]] start = 6000 end = 6010 - 被穿透的 SSH 本身改用密钥登录、禁用密码登录、fail2ban 照常开。
- 主机名/IP、密码、token 提交到任何公开仓库前一律打码,compose 与 toml 不进公开 git。
五、常见问题
| 现象 | 原因与处理 |
|---|---|
客户端日志 authorization failed |
token 不一致或有空格/引号问题,逐字核对两端 auth.token |
dial tcp ...:7000 i/o timeout |
安全组没放 7000,或服务端没起来;ss -ltnp 在服务器上确认 |
| proxy 注册成功但连 6000 超时 | 安全组没放 remotePort;或该端口被其他进程占用(ss -ltnp \| grep 6000) |
| SSH 能连但很卡/频繁断 | 中间链路问题可在 proxy 下加 transport.proxyProtocolVersion/心跳设置;默认 heartbeatInterval = 30、heartbeatTimeout = 90 一般不用动 |
| HTTP 域名代理 404 | 服务端要配 vhostHTTPPort,域名 DNS 要指向服务器,访问端口与 vhost 端口一致 |
| 客户端时间错误导致鉴权异常 | 校准时间:chronyd/ntpdate,容器用 host 网络会直接用宿主时间 |
| 升级后客户端起不来 | 0.52 起是 TOML 配置,老的 INI 写法已废弃;两端保持同一大版本 |
此处评论已关闭