这几年我帮朋友和自己折腾过好几套内网穿透方案从最初图省事用 ngrok到后来在真实服务器上长期跑 frp来回对比过不少次。frp 是一个完全开源的轻量级工具用 Go 编写服务端和客户端就是两个二进制文件部署起来干净利落目前在 GitHub 上的热度一直很高也是社区里公认最灵活的内网穿透方案之一。这篇文章是这个系列的第一篇我会把 frp 的原理、配置文件写法、常见坑一次性讲清楚。适合两类人看一类是刚接触内网穿透、想把家里 NAS 或工作室服务器安全地暴露到公网的新手另一类是已经在用 frp、但只停留在“能通就行”阶段、想系统梳理一遍配置细节的开发者。内网穿透这个需求本质上是把没有公网 IP 的设备通过一台有公网 IP 的服务器作为跳板让外部网络可以反过来访问到它。frp 做的就是这件事而且做得足够彻底支持 TCP、UDP、HTTP、HTTPS还能做 STCP 点对点加密通道。接下来我从为什么选它开始逐步讲到配置实战。1. 为什么选择 frp 做内网穿透1.1 内网穿透到底解决什么问题很多场景下你的设备并没有一个可以直接从互联网访问的地址。比如家里宽带运营商只分配了一个动态的内网 IP或者公司办公网的机器被 NAT 挡住了。这时候你想在外面用 SSH 连回办公室的电脑、想临时让客户看一眼本机跑着的原型页面、想在外网访问家里的 NAS 管理界面都会直接卡住。内网穿透的思路是让内网设备主动向外网服务器发起一条连接然后持续保持这条连接。外网服务器收到访问请求后通过这条已有连接把流量转发到内网设备。你可以把它理解成一条一直挂着的电话线——内网设备先给服务器打了个电话之后谁来服务器找这个人服务器都通过这条电话线把声音转进去。所以 frp 的核心不复杂就是“主动建连、长连接保活、双向转发”。我实际项目里最常用的几个场景是远程 SSH 登录办公室 Linux 机器、外网访问群晖 DSM 管理页面、把本机的 Web 开发服务器暴露给测试人员访问、给微信小程序的回调接口提供一个公网可达地址。这些事情靠路由器端口映射在部分环境下也能做但一旦处于多层 NAT 之后的网络端口映射基本失效frp 这种主动外联的方式才真正可靠。1.2 frp 对比同类开源工具的取舍市面上开源的内网穿透工具不算少ngrok、nps、frp 是最常被拿出来对比的三个。ngrok 的优势是有官方托管服务拿到命令就能跑但如果要自建 ngrok 服务端配置和编译流程相对繁琐而且它本身的定位更偏向“临时把本地服务暴露出去给人家看一下”。nps 的特点是带一个比较完整的管理后台适合团队内做服务管理但部署度和灵活度上不如 frp 直接。frp 在开源社区里能长期占据主流位置主要因为三点。第一单文件分发服务端 frps、客户端 frpc 各是一个二进制不依赖 JVM、不依赖 Python 运行时丢到服务器上就能跑。第二协议类型覆盖广TCP、UDP、HTTP、HTTPS、STCP、XTCP 都有从简单的端口映射到点对点加密通道都能做。第三社区活跃度非常高官方仓库的 issue 响应快文档也比较完整遇到问题搜一下基本都有答案。frp 的另一个优势是可控性。它的服务端和客户端都在你自己手里token 鉴权、TLS 加密、端口隔离都可以自己配置不受第三方隧道服务的流量和域名限制。所以一旦有长期稳定的需求自建 frp 基本是绕不开的选项。2. 部署前的架构梳理与环境准备2.1 公网服务器与内网客户端的角色划分frp 架构里有两类角色角色搞混了后面配置肯定会乱。第一类是“服务端”也就是 frps必须部署在一台有公网 IP 的服务器上。这台服务器是你的流量中转站所有外部访问都会先到达这里。它需要一个独立的端口来和 frpc 建立控制连接这个端口默认是 7000也是我实际部署时最常使用的端口。第二类是“客户端”也就是 frpc部署在你真正想暴露的内网设备上。不要被名字误导frpc 不是“客户端访问工具”而是“内网侧的服务注册端”。它做的事情是把自己的内网服务告诉服务端然后保持控制连接不断开。配置里的每一条代理规则都是在声明“我想要把内网这台机器上的哪个端口映射到公网服务器的哪个端口”。所以网络拓扑一般是这样的公网用户访问你的服务器 IP:远程端口→ 服务器把流量推给已经保持连接的 frpc → frpc 将流量送到内网服务的 local IP 和 local Port。中间那台公网服务器配置最低的 1 核 1G 就够了因为 frp 自身开销很小主要消耗在流量转发上。2.2 获取 frp开源仓库下载与版本选择frp 的官方仓库是 GitHub 上的 fatedier/frp开源协议是 Apache 2.0可以放心用于个人项目和商业项目。下载方式很简单到 Releases 页面找对应平台的压缩包即可。服务器端几乎都是 Linux x86_64内网客户端可能是 Windows、macOS、树莓派的 ARM 架构甚至路由器里的 ARM 硬路由下载前先确认好 CPU 架构。以 Linux x86_64 为例下载压缩包后解压目录里会看到 frps、frpc 两个可执行文件以及对应的 .toml 配置文件。这里我特别提醒一点frp 从 v0.52.0 开始支持 TOML 配置格式v0.53.0 之后完全移除了对旧版 INI 格式的支持。如果你在网上搜到老教程还在写[common] server_addr x.x.x.x那基本都是旧版内容放到新版本里是跑不起来的。现在直接使用.toml格式所有新特性也都只在新格式里支持。如果你用的是 Docker 方式部署镜像方面社区维护比较活跃的包括 snowdreamtech 和本地自构建的镜像。我的习惯是服务器端直接二进制部署做 systemd 管理内网设备上用 Docker 或二进制按情况选择。二进制部署的好处是排障直观docker 部署的好处是升级方便两条路我后面都会讲。3. 服务端配置实操frps.toml3.1 最小可用配置bindPort 与 token第一件事是先写服务端配置。新建一个frps.toml最小可用配置如下bindPort 7000 auth.method token auth.token 改成你自己的强随机字符串bindPort是 frps 监听的控制连接端口frpc 端的serverPort要和它保持一致。这个端口同时承载控制连接和数据转发所以防火墙和云平台安全组一定要放行。auth.token是客户端和服务端之间的共享密钥相当于整个 frp 体系的通行证。不要图省事用弱密码我见过有人把 token 设为 123456结果公网服务器被别人扫描到端口后直接被蹭流量转发。有些部署教程还会提到bindAddr默认是0.0.0.0也就是监听所有网卡。如果你有多网卡想把 frps 限制在某一个 IP 上可以显式设置。单 IP 的云服务器基本不用动。配置写好后启动服务端./frps -c ./frps.toml看到日志里出现start frps success并且监听了 7000 端口服务端就绪。为了验证端口确实通了本地电脑上用telnet 你的服务器IP 7000测一下能通再继续配置客户端别一上来就让 frpc 反复重试。3.2 开启 Dashboard 面板随时看连接状态frp 自带一个轻量的 Web 管理面板 Dashboard虽然不是必须的但我强烈建议开启。它能看到当前哪些客户端在线、哪些代理规则成功建立、实时流量统计数据排障时非常有用。在frps.toml里增加这段webServer.addr 0.0.0.0 webServer.port 7500 webServer.user admin webServer.password 给面板单独设一个强密码这里webServer.port是面板端口注意它和bindPort是两回事。addr建议设成0.0.0.0因为很多时候你需要从外部访问面板只绑 127.0.0.1 会导致只有服务器本机能打开。改完配置后重启 frps浏览器访问http://你的服务器IP:7500输入用户名和密码就能看到面板。面板上有一个很关键的信息每个代理的“状态”。当 frpc 配置有问题时这里的代理会显示异常或不存在。另外提醒一句Dashboard 本身没有 HTTPS生产环境建议放在内网访问或者用 Nginx 反代加一层 TLS不要直接裸奔在公网上否则面板密码一旦泄露等于把穿透服务的控制权交给了别人。4. 客户端配置实操frpc.toml4.1 TCP 代理先用 SSH 打通一条隧道服务端就绪后接下来是内网设备的客户端配置。拿最常用的 SSH 远程登录场景举例内网一台 Linux 机器开启了 22 端口公网服务器 IP 假设是1.2.3.4我想在外部通过1.2.3.4:6000访问它的 22 端口。frpc.toml配置如下serverAddr 1.2.3.4 serverPort 7000 auth.method token auth.token 和服务端一致的token [[proxies]] name my-ssh type tcp localIP 127.0.0.1 localPort 22 remotePort 6000启动 frpc./frpc -c ./frpc.toml看到日志里代理注册成功就可以在外部测试了ssh -p 6000 user1.2.3.4这条链路是怎么走的外部 SSH 客户端连服务器 6000 端口服务器把数据通过 frp 控制通道交给内网 frpcfrpc 再把数据发往127.0.0.1:22。因为 frpc 就在目标机器上所以localIP写 127.0.0.1 没问题。如果你要访问的是内网另一台机器的服务比如一台群晖 NAS 的 IP 是192.168.1.100那localIP就写192.168.1.100localPort写5000群晖 DSM 默认端口。同理Windows 远程桌面就是localPort 3389外部映射一个不冲突的端口如13389。这里有一个常见概念要纠正remotePort 不是随便选的它必须能通过服务器防火墙和安全组同时不能和已有端口冲突。如果你映射了一堆服务建议用表格把 name、localPort、remotePort 记录清楚否则后期根本想不起来 16000 是哪个服务。4.2 HTTP/HTTPS 代理给内网 Web 服务加域名如果你需要让外网用户通过域名访问内网的 Web 服务用 TCP 映射加端口虽然也能通但体验很糟。更好的方式是用 frp 自带的 HTTP 代理类型让 frps 帮你做域名到内网服务的映射。先在服务端frps.toml里加一行启用虚拟主机端口vhostHTTPPort 80这个端口是 frps 用来接收 HTTP 流量的入口。注意如果服务器上已经有 Nginx 占用 80要么把 Nginx 停掉要么换一个端口。生产环境我更建议让 Nginx 监听 80然后用 Nginx 反代到 frp 的 vhostHTTPPort。客户端配置[[proxies]] name web-demo type http localIP 127.0.0.1 localPort 8080 customDomains demo.example.com这里customDomains是要绑定到该服务的域名。配置生效后第一步把demo.example.com的 A 记录解析到你的公网服务器 IP第二步访问http://demo.example.comfrps 就会根据 Host 头把请求路由到内网这台机器的 8080 端口。如果服务本身是 HTTPS就用type https在服务端配置vhostHTTPSPort 443。不过 HTTPS 涉及证书配置通常我会选择在 frps 前面加一层 Nginx用 Nginx 处理证书和 HTTPS 终结再反代到 vhostHTTPPort。这样证书管理更灵活也方便做日志和限流。5. 进阶玩法TLS 加密、STCP 与 Docker 部署5.1 启用 TLS 加密通道不再裸奔frp 默认的控制连接和数据转发是明文传输。在不可信网络下如果中间链路被监听流量内容是可见的。虽然 frp 主要跑在你自己控制的服务器之间但公网传输的私密性仍然值得重视。服务端配置开启强制 TLStransport.tls.force true客户端配置transport.tls.enable true开启后 frpc 和 frps 之间所有的通信都会走 TLS 加密能有效防止中间人窃听。如果你在 frp 前面还要套一层 Nginx 做域名转发也可以额外用 wss 的方式来承载不过 frp 自身的 TLS 已经解决了加密问题大多数业务场景不需要再叠加。我个人的习惯是只要是跨公网的 frp 链路一律开启 TLS。代价几乎可以忽略换来的是数据不再裸奔。很多用户以为 frp 默认就是加密的这是个误区一定要主动配置。5.2 STCP 点对点通道连端口都不用暴露普通 TCP 代理会在公网服务器上暴露一个 remotePort这意味着任何人都可以尝试连接这个端口。如果你只希望少数几台设备访问STCP 是更安全的选择。STCP 的全称是 Secret TCP它不暴露远程端口而是让两个 frpc 之间通过 frps 交换连接信息。只有知道secretKey的另一台设备才能发起访问。举个例子你希望自己在笔记本上能安全地 SSH 到家里的服务器但不想让家服务器的 SSH 端口被公网扫描到。家里服务器的 frpc 配置[[proxies]] name secret-ssh type stcp secretKey 你的共享密钥 localIP 127.0.0.1 localPort 22笔记本上的 frpc 配置[[visitors]] name secret-ssh-visitor type stcp serverName secret-ssh secretKey 同一个共享密钥 bindAddr 127.0.0.1 bindPort 6000这样在笔记本上执行ssh -p 6000 user127.0.0.1访问的是笔记本本地 6000 端口然后 frp 通过加密隧道转到家里服务器的 22 端口。公网服务器上根本看不到任何开放的业务端口端口扫描扫不出东西。这个特性特别适合保护 SSH、数据库管理端口这类高价值服务。我在生产环境里管理多台服务器用的就是 STCP 方案外部攻击面小了很多。5.3 Docker 部署与开机自启运维友好二进制部署的优点是直观但如果是 Docker 环境或者你想省掉 systemd 文件的编写用 Docker 部署更省心。服务端示例docker run -d \ --name frps \ --restartalways \ --network host \ -v /opt/frp/frps.toml:/etc/frp/frps.toml \ snowdreamtech/frps:latest我特意加了--network host因为 frp 需要监听多个端口host 模式能避免逐一映射端口的麻烦。如果你不想用 host 模式就要把 bindPort、webServer.port、以及所有可能用到的 remotePort 全部用-p映射出来会比较繁琐。客户端同理docker run -d \ --name frpc \ --restartalways \ --network host \ -v /opt/frp/frpc.toml:/etc/frp/frpc.toml \ snowdreamtech/frpc:latest如果不想用第三方镜像也可以自己写一个极简 Dockerfile把官方二进制和配置放进去。但就我的经验社区维护的镜像更新跟得上官方版本直接用来省事只要确认镜像来源可信即可。二进制部署下开机自启用 systemd 管理。创建一个/etc/systemd/system/frpc.service[Unit] Descriptionfrp client Afternetwork.target [Service] ExecStart/usr/local/bin/frpc -c /etc/frp/frpc.toml Restartalways RestartSec5 [Install] WantedBymulti-user.target然后systemctl enable --now frpc即可。这里有个细节Restartalways很关键内网设备的网络可能不稳定frpc 断线后需要自动重连否则人不在现场时穿透服务就悄悄挂了。6. 常见问题与排查实录6.1 面板登不上、连接不稳定怎么办Dashboard 面板无法访问我排查的顺序是第一确认webServer.addr不是 127.0.0.1否则外部浏览器访问不了第二确认云平台安全组和服务器防火墙都放行了 7500 端口第三用curl -I直接测面板地址看返回码。多数情况都是前两步的问题。连接不稳定通常表现为 frpc 日志里反复出现login to server failed或connection closed。先看 token 是否一致再看 frps 和 frpc 的版本是否差距过大。frp 大版本升级时配置格式和协议都可能变化新旧版本混用容易出现各种奇怪现象。我的经验是尽量让两端版本保持一致至少保证小版本接近。还有一个容易忽略的细节如果 frps 开启了 TLS force而 frpc 没有开启 TLS连接必定失败。配置项较多时改完务必检查一遍两端配置的对应关系。6.2 端口冲突、防火墙与安全组远程端口连不上先别急着怀疑 frp 配置先确认服务器防火墙。很多云服务器有两层防火墙系统自带的 firewalld/ufw以及云平台控制台里的安全组。我遇到最多的情况是系统防火墙放行了安全组没放行或者反过来。检查命令firewall-cmd --list-ports ufw status如果用的是 firewalld开放端口firewall-cmd --permanent --add-port7000/tcp firewall-cmd --permanent --add-port7500/tcp firewall-cmd --permanent --add-port6000/tcp firewall-cmd --reload同时要去云平台安全组把对应端口加入放行规则。云平台的安全组通常看作第二道门这道门不放行系统防火墙放行也没用。端口冲突也是常见问题。比如 remotePort 设了 6000但服务器上已有其他服务占用 6000frps 启动会报错或者代理建立失败。我的做法是规划一组专用端口段比如远程管理类用 60000-61000 段每个服务固定分配尽量避免与常用端口冲突。6.3 配置格式与版本兼容问题网上很多老教程还是 INI 格式的 frps.ini新版本装上后直接报错。新版 frp 只认 TOML 格式后缀名是.toml。如果你照着老教程改配置发现不生效先检查版本号再看看配置文件后缀。v0.53.0 之后 INI 格式已不再支持这是最典型的“照抄老文章翻车”场景。另一个容易踩坑的是代理名字不能重复。如果你在 frpc.toml 里写了两条 name 相同的代理后一条会覆盖前一条或者直接注册失败。给每条代理起名时用项目-用途-端口的格式比如nas-dsm-5000、mac-rdp-3389避免重复也方便后期维护。日志里出现[frpc] login to server failed: token error说明 token 不匹配出现proxy name is already registered说明代理名冲突出现port already used说明端口被占用。多看日志很多问题不用猜frp 的日志信息已经写得很清楚了。还有一点frp 面板或客户端显示在线但外部访问就是不通。排查思路是先在服务器本地测试curl 127.0.0.1:remotePort通的话说明 frps 正常问题可能出在安全组不通的话看 frps 日志里代理会话建立、代理工作状态是否正常再逐层往上查。7. 写在最后的几个实际体会我自己运维 frp 这些年踩过最大的坑反而不是配置本身而是“忘了重启”和“安全组没放行”这两件事。frp 的配置是启动时读取的改完配置不重启服务配置永远不会生效。很多朋友折腾半天最后发现就是没重启。建议每次改完配置都形成固定动作重启服务、看日志、验证端口连通。另一个体会是frp 虽然部署简单但安全一定要做足。token 用强随机字符串面板密码单独设置开放公网的 remotePort 越少越好。能用 STCP 的场景就不要用普通 TCP养成最小暴露面的习惯。这个系列到这里先告一段落。后续我打算把 STCP 在真实多机环境里的批量管理、frp 结合 systemd 的完整上电自启、以及和 Nginx 搭配做多域名反代的细节继续展开这些都是我在生产环境中反复验证过的方案希望系列文章能帮你把 frp 用得更顺。