用 frp 做内网穿透时的几个配置要点
frp 的结构很简单:一台有公网 IP 的服务器跑 frps,
内网机器跑 frpc 主动连上去,由服务端把公网端口上的连接转发到内网。
配置本身不复杂,但有几个点不注意会带来安全或运维上的麻烦。
最小可用配置
服务端 frps.toml:
bindAddr = "0.0.0.0"
bindPort = 7000
auth.method = "token"
auth.token = "换成一串足够长的随机值"
webServer.addr = "127.0.0.1"
webServer.port = 7501
webServer.user = "admin"
webServer.password = "另一串随机值"
log.to = "/var/log/frps.log"
log.level = "info"
log.maxDays = 7
客户端 frpc.toml:
serverAddr = "公网服务器地址"
serverPort = 7000
auth.method = "token"
auth.token = "与服务端一致"
[[proxies]]
name = "home-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 13322
127.0.0.1,不要跟 frps 一起暴露到公网。
需要看面板时用 SSH 隧道:ssh -L 7501:127.0.0.1:7501 服务器。
allowPorts:限制能被占用哪些公网端口
默认情况下 frpc 想占哪个 remotePort 都行。这意味着一旦 token 泄漏,
对方可以把服务端上任意空闲端口占走,包括你留给别的用途的口。
用 allowPorts 把它收窄:
allowPorts = [
{ start = 22, end = 22 },
{ start = 80, end = 80 },
{ start = 443, end = 443 },
{ start = 10000, end = 20000 },
]
改了之后记得 systemctl restart frps,并且同步确认云厂商的安全组也放行了对应端口——
安全组没开的时候,frpc 能连上、映射也显示成功,但外网访问不到,很容易误判成 frp 的问题。
remotePort 冲突
同一个 remotePort 在同一时刻只能被一个 proxy 占用。
如果改了配置重启后映射没生效,先看 frps 日志:
grep -i "port already used\|proxy \[.*\] already exists" /var/log/frps.log
另外要注意别和服务端本机已在监听的口撞车。比如服务端跑了 nginx(80)和 sshd(22), 这时再让 frpc 去占 22 就会失败。
容易被忽略的一点:来源 IP 全变成 127.0.0.1
frpc 是从内网机器本地发起连接的,所以被映射的服务(SSH、Web、远程桌面)看到的
所有请求来源都是 127.0.0.1。这会带来两个实际后果:
- 基于 IP 的封禁失效。fail2ban、RDPGuard 这类按来源 IP 封的工具 在这里完全没用,因为所有攻击看起来都是本机在连自己。
- 日志失去溯源能力。安全日志里只有本机地址,无法区分正常访问和外部扫描。
实际经历是:把远程桌面通过 frp 暴露到公网后,一周内本机安全日志记录了
两万多次登录失败,来源全部是 127.0.0.1,尝试的账号是
admin、administrator、root 这类字典词。攻击没成功(只开了密钥认证),
但日志被彻底刷满,排查自己的问题时反而找不到记录。
ssh -p 13322 -L 3390:127.0.0.1:3389 服务器,
然后本地连 127.0.0.1:3390。SSH 只开密钥认证,攻击面小得多,还能审计。
把 frpc 做成开机自启
Linux 上直接用 systemd(写法见本系列的另一篇)。Windows 上用计划任务, 触发器选「计算机启动时」,账户选 SYSTEM,这样不用等用户登录就能连上。
排查顺序
- frpc 进程在不在:
ps aux | grep frpc - 客户端日志有没有连上:
tail -50 frpc.log,看login to server success - 服务端有没有注册成功:
grep "\[home-" /var/log/frps.log | tail - 服务端端口在不在监听:
ss -ltn | grep 13322 - 本机服务本身通不通:
curl 127.0.0.1:端口 - 安全组放行了没有
按这个顺序走,基本能定位到是哪一层的问题,不用靠猜。