Linux内网穿透实战:frp自建隧道从原理到安全加固
先把话放前面这个标题里的“liunx”我猜是想写Linux这种拼写错误在搜索引擎里其实很常见我也经常看到有人这么敲所以这篇直接按Linux来写。内网穿透这活儿说起来不复杂但真要在Linux系统上把它跑通、跑稳、跑得让人放心里面还是有不少门道的。这篇文章我不会只丢一个工具链接给你而是把原理、选型、搭建、踩坑、加固整个链路都过一遍适合正在折腾VPS和家里/公司内网机器的朋友也适合那些 SSH 都连不上、但急需远程操作内网服务器的运维新手。先说清楚它到底解决了什么问题你有一台Linux机器放在公司机房或者家里路由器后面没有公网IP外面的人访问不到。内网穿透就是在你有一台公网服务器一般叫VPS的前提下让公网用户通过VPS的中转反过来连到内网里的这台Linux机器。用一句话概括外网访问内网靠的不是“打洞”而是“拉一根反向的线”。1. 先弄清楚一个前提你的服务为什么不能被公网访问很多人一上来就急着装工具结果连“访问不了”这件事的根本原因都没搞清楚。我建议先花几分钟把网络模型理顺后面排查问题会快得多。1.1 内网的隔离逻辑NAT与私有IP绝大多数家庭宽带和公司网络用的是私有IP段典型的就是192.168.x.x、10.x.x.x、172.16.x.x。这套地址只在内网有效出了路由器就被丢弃了公网上的设备根本没法直接寻址到你的机器。原因很简单IPv4地址资源早就枯竭了运营商会给每个家庭/企业只分配一个公网IP甚至很多地方分配给你的还是个大内网IP也就是运营商层面的NAT连公网IP都不是。在这种结构下你内网机器上的SSH服务22端口、Web服务80/8080端口默认只监听在内网接口上公网用户发起请求时数据包到了你家路由器的WAN口就被拦下来了。路由器不知道这个数据包该转发给哪台内网机器除非你手动做端口映射Port Forwarding但即便做了映射运营商不给你公网IP映射也是白搭。1.2 穿透的本质拉一根反向连接既然外网主动连不进来那就换个思路让内网机器主动往外连。这就是所有内网穿透工具的底层逻辑内网机器上的客户端进程主动和公网VPS上的服务端进程建立一个长连接。这个连接建立之后公网用户访问VPS的某个端口VPS就把这个流量顺着这条已有的长连接“塞”给内网客户端内网客户端再把流量转发给你本地的SSH、Web或者其他服务。打个比方你家住在一个没有门牌号的小区内网外人找不到你。但你有一部能打给物业总机的电话长连接你先拨通了物业VPS说“我要找张三”。之后外面的人想找你就打电话给物业物业再通过这通已经接通的电话转告你。整个过程里你不需要有固定门牌号只需要保证那通电话长连接不断。理解了这个模型后面配置FRP时你就会知道为什么服务端要有个bindPort控制端口为什么客户端要配置serverAddr和serverPort为什么隧道建立后外部流量走的是remotePort。本质上就是两条通道一条是控制信令通道长连接一条是数据转发通道公网端口到内网端口。2. 内网穿透工具怎么选ngrok、cpolar、frp、樱花Frp的取舍市面上的内网穿透方案不少我先列一下主流几个再讲我怎么选。懒人可以直接跳到结论。维度ngrokcpolar樱花Frpfrp自建是否需要公网服务器不需要不需要不需要需要自定义域名/端口免费版受限部分付费部分受限完全可控数据经过第三方是是是否免费带宽/速度有限制有限制看节点线路取决于VPS带宽长期稳定性一般一般一般取决于自己服务器上手难度低低低中2.1 托管类工具ngrok、cpolarngrok是老牌选手注册账号、下载客户端、运行命令就能拿到一个公网地址。它最大的优势是零门槛内网机器不需要有公网IP也不用自己买VPS。缺点也很明显免费版域名随机每次启动可能变带宽被限制传输敏感数据时你完全不知道数据经过哪些节点。国内直连ngrok的服务器还经常抽风体验不稳定。cpolar是国内团队做的中文文档友好命令行交互清晰适合快速做演示、调试Webhook回调这类场景。它也支持创建TCP隧道临时把内网的SSH暴露出去。同样的问题免费额度有限如果要稳定的域名和更大的带宽得付费。作为临时调试工具cpolar和ngrok都不错但作为生产级长期方案我不太推荐。2.2 半托管类樱花Frp樱花Frp严格来说是一个frp的公益托管平台你在它的网站上创建隧道下载它的客户端也是基于frp改的就能把内网服务穿透出去。它对个人用户很友好甚至提供了免费线路适合个人博客、游戏联机、临时演示这类场景。但因为节点资源是共享的高峰期速度不保证而且你依赖第三方的稳定性。它适合不折腾、不想买VPS的人不适合对数据链路有掌控要求的场景。2.3 自建首选frp我最终的选择frp是开源项目GitHub上星标极高Go语言写的单文件部署跨平台。它支持TCP、UDP、HTTP、HTTPS还能做STCP、XTCP这种点对点加密代理能力非常全面。自建frp需要一台有公网IP的VPS但好处是数据路径完全由自己掌控端口、域名、带宽、鉴权全都自己说了算。我最终选择frp原因有三点。第一VPS本来就常年开着闲置带宽不用白不用。第二frp的服务端frps和客户端frpc都是单二进制文件部署就是复制文件的事没有任何依赖。第三社区活跃遇到问题搜一搜基本都有答案。如果你手里已经有了一台公网Linux服务器不管配置多低1核512M就够跑frps了自建frp都是长期最稳的方案。3. 基于frp自建穿透服务从VPS准备到客户端配置的完整搭建下面进入正题我用最典型的场景来演示内网一台Linux主机跑着SSH22端口和一个Web服务8080端口我希望能从公网SSH登录到这台机器也能通过VPS访问到内网的Web服务。3.1 准备阶段VPS、域名、端口规划你需要准备的东西一台有公网IP的Linux VPS我以Ubuntu 22.04为例CentOS也同理一台内网Linux主机目标机器可以是Ubuntu、Debian、CentOS一个域名可选。如果你只想穿透SSH/TCP不需要域名如果要穿透HTTP有个域名体验会舒服很多免去访问时需要带端口的麻烦端口规划是很多人忽略的一步。我建议按下面的原则分配frps控制端口不使用默认的7000改成比如17000减少被扫描器盯上的概率SSH代理remotePort比如16022避免和VPS自身的22冲突HTTP代理vhost端口比如18080避免和VPS上已有的Nginx/Web服务占用的80端口冲突frp Dashboard端口比如17500管理页面用表格整理一下我用的端口分配用途端口说明frps控制通道17000frpc连接frps用SSH远程端口16022公网 ssh -p 16022 访问内网机器HTTP虚拟主机端口18080配合域名做Web穿透Dashboard管理页17500frp自带的监控面板3.2 服务端部署frps去GitHub的frp发布页下载最新版注意选对架构。x86_64的机器选linux_amd64ARM64的选linux_arm64。用uname -m确认一下树莓派或者部分国产ARM服务器经常在这里翻车。# 以frp 0.61.1版本为例 wget https://github.com/fatedier/frp/releases/download/v0.61.1/frp_0.61.1_linux_amd64.tar.gz tar -zxvf frp_0.61.1_linux_amd64.tar.gz cd frp_0.61.1_linux_amd64 sudo cp frps /usr/local/bin/ sudo mkdir -p /etc/frp sudo cp frps.toml /etc/frp/然后编辑服务端配置文件 /etc/frp/frps.tomlbindAddr 0.0.0.0 bindPort 17000 # 认证令牌frpc连接时必须携带相当于通信密码 auth.method token auth.token 请改成足够长的随机字符串 # 管理面板方便看连接状态和流量 webServer.addr 127.0.0.1 webServer.port 17500 webServer.user admin webServer.password 改成强密码 # HTTP穿透的虚拟主机端口 vhostHTTPPort 18080注意我特意把webServer.addr设成了127.0.0.1而不是0.0.0.0。原因是Dashboard管理页面不应该直接暴露到公网如果你想看面板用SSH隧道或者下面提到的安全组策略做限制而不是裸奔。启动服务端验证一下frps -c /etc/frp/frps.toml看到类似“frps started successfully”的日志就说明服务端起来了。这时候VPS的17000端口应该处于监听状态用ss -lntp | grep 17000确认。3.3 客户端部署frpc在内网Linux机器上执行同样的下载和解压注意这次我们只需要frpc和frpc.tomlwget https://github.com/fatedier/frp/releases/download/v0.61.1/frp_0.61.1_linux_amd64.tar.gz tar -zxvf frp_0.61.1_linux_amd64.tar.gz cd frp_0.61.1_linux_amd64 sudo cp frpc /usr/local/bin/ sudo mkdir -p /etc/frp sudo cp frpc.toml /etc/frp/编辑客户端配置 /etc/frp/frpc.tomlserverAddr 你的VPS公网IP serverPort 17000 auth.method token auth.token 和frps.toml里保持一致 # 启动时如果连不上服务端不要立即退出而是继续等。 # 这个参数对开机自启非常关键避免网络还没就绪时服务直接挂掉。 loginFailExit false # 第一个隧道SSH [[proxies]] name ssh-internal type tcp localIP 127.0.0.1 localPort 22 remotePort 16022 # 第二个隧道内网Web服务 [[proxies]] name web-internal type http localIP 127.0.0.1 localPort 8080 customDomains [blog.example.com]启动客户端frpc -c /etc/frp/frpc.toml3.4 连通性验证验证SSH穿透是否生效在任意一台公网机器上执行ssh -p 16022 root你的VPS公网IP如果能登录到内网机器说明TCP穿透链路已经通了。验证Web穿透需要先确保域名解析到了VPS的IP然后访问 http://blog.example.com:18080正常情况下应该能看到内网Web服务返回的页面。这里我解释一下Web穿透的流量路径很多人第一次看会绕晕用户访问 blog.example.com:18080DNS解析blog.example.com得到VPS的IP请求到达VPS的18080端口frps收到frps根据HTTP请求里的Host字段blog.example.com匹配到名为web-internal的隧道frps把这个请求打包通过17000的长连接发给内网机器的frpcfrpc将请求转发给本机127.0.0.1:8080的Web服务Web服务的响应再原路返回整个过程对用户来说是透明的他只会觉得这个网站就在VPS上。3.5 为什么remotePort不能随意乱选很多人随手把remotePort配成80、443这种常用端口然后在VPS上发现起不来。原因很直接VPS本身的Nginx、Apache或者系统服务已经占用了这些端口。既然frp的端口规划是“公网侧端口”你就要把VPS看成一个独立的公网主机它的端口资源和内网机器完全隔离。所以配置remotePort之前先用ss -lntp看看目标端口在VPS上是否已被占用。同理customDomains指向的域名如果你想用80端口访问Web服务那你的VPS上必须能腾出80端口给frps的vhostHTTPPort。如果80被Nginx占了要么改Nginx做反向代理到vhostHTTPPort要么就别用80端口访问时带着端口号。4. 踩坑实录穿透服务跑不起来时我是怎么一步步定位的以下是真实遇到过的高频问题我按排查链路写出来。这几个坑我敢保证你大概率会踩到至少一个。4.1 第一坑云安全组没有放行端口导致“明明配置全对就是连不上”这是最隐蔽也最常见的问题。你的frps起来了ss也能看到端口监听但外部就是连不上。原因是阿里云、腾讯云、AWS这些云厂商的安全组策略默认只放行22、80、443等少量端口其他端口一律丢弃。你敲了ss -lntp看到端口在监听那是在服务器内部视角而安全组是在云端网络层面拦截的。排查思路先别急着怀疑frp配置。从你的本地机器上用telnet或者nc测试一下VPS端口通不通nc -vz 你的VPS公网IP 17000 nc -vz 你的VPS公网IP 16022如果显示Connection refused并且你确认frps进程活着那大概率是安全组的问题。登录云控制台找到安全组规则放行17000、16022、18080这三个入口方向端口。放行之后重新测试这是99%的“服务端明明正常但客户端连不上”的答案。4.2 第二坑版本更新导致配置格式不兼容老教程直接抄会报错frp在0.52.0版本之后配置格式从INI格式换成了TOML格式。网上大量老教程还在讲 [common] server_addr xxxx如果你直接用新版frp去跑老配置一定会报错。新版应该是 serverAddr xxxx 这种更简洁的风格。而且代理配置的写法也变了老版是用 [ssh] type tcp 这种带中括号的段落独立定义新版是用 [[proxies]] 数组形式然后name字段做区分。我在这篇博文里写的就是新版格式建议你下载最新版之后直接参考官方release包里的frps.toml和frpc.toml模板不要盲目复制网上三年前的配置。排查思路启动frpc时报类似“toml: line X: key server_addr is not expected”的错误基本就是配置格式问题。到下载的目录里看官方的frpc.toml示例对着改字段名就行。4.3 第三坑token不一致握手静默失败frps和frpc都配置了auth.token只要有一个字符不一致连接就会握手失败但诡异的是frps日志里可能只显示一条不太起眼的信息而frpc一直报“login to server failed: EOF”或者“connection closed”。我第一次遇到这个坑时反复检查了防火墙和端口最后才发现是两个配置文件的token里有个空格没删干净。排查思路先去frps日志里看有没有token验证失败的记录然后在frpc里执行frpc -c frpc.toml -v用详细模式启动它会打印握手过程token校验失败时会有明确的提示。另外建议token直接用openssl rand -base64 24生成不要手敲避免复制粘贴时带上换行符或空格。4.4 第四坑内网服务的localIP写成了别的机器IPlocalIP这字段指的是你内网那台目标机器的IP。如果你frpc和SSH服务跑在同一台机器上写127.0.0.1完全没问题。但如果你在内网一台跳板机上跑frpc想穿透另外一台数据库服务器那localIP就得写那台数据库服务器的内网IP比如192.168.1.50而且确保跳板机和数据库服务器之间网络通、防火墙放行。这个坑的诡异之处在于frpc启动时完全不校验localIP和localPort是否可达。配置里随便写一个不存在的内网地址frpc照样显示代理启动成功但外部访问就是不通。所以排查时要看frpc日志如果出现“connect to local service error”之类的记录赶紧检查localIP和localPort是不是写错了或者服务本身没起来。4.5 第五坑systemd守护进程的管理细节很多人直接nohup ./frps -c frps.toml 就跑了看起来没问题但服务器一重启服务就没了。内网穿透这种东西连接断了就相当于远程失联所以必须交给systemd托管并且设置自动重启。服务端和客户端的unit文件写法如下先服务端 /etc/systemd/system/frps.service[Unit] DescriptionFRP Server Afternetwork.target [Service] Typesimple ExecStart/usr/local/bin/frps -c /etc/frp/frps.toml Restarton-failure RestartSec5s [Install] WantedBymulti-user.target客户端 /etc/systemd/system/frpc.service[Unit] DescriptionFRP Client Afternetwork.target [Service] Typesimple ExecStart/usr/local/bin/frpc -c /etc/frp/frpc.toml Restartalways RestartSec5s [Install] WantedBymulti-user.target然后执行sudo systemctl daemon-reload sudo systemctl enable --now frps sudo systemctl enable --now frpc注意客户端我用了Restartalways服务端用Restarton-failure。原因是客户端如果因为网络抖动退出重启策略必须是无条件的而服务端正常停止时没必要反复拉起。5. 从“能跑”到“好用”多客户端接入、系统守护与安全加固穿透链路一旦通了你会发现它的用处绝不只是SSH。但“能跑”和“生产环境能用”之间还有一段路要走我给你列一下我实践后觉得必须做的几件事。5.1 systemd守护不再赘述但记得处理启动顺序上面已经把unit文件写清楚了这里补充一个容易忽略的点如果你的内网机器设置了开机自启但网络启动较慢frpc可能在网络就绪前就开始尝试连接服务端导致启动失败。除了配置loginFailExit false之外还可以在unit文件里给frpc加一句Afternetwork-online.target Wantsnetwork-online.target这样systemd会等网络完全就绪后再拉起frpc配合Restartalways基本能做到开机即恢复。5.2 多客户端接入怎么在同一台VPS上区分多台内网机器frp的设计天然支持多客户端也就是说一个frps可以同时服务多个内网机器。你只需要在每台内网机器上部署frpc配置相同的serverAddr和token但确保每台机器里的[[proxies]] name和remotePort不重复就行。例如一号内网机器用代理名 ssh-host1remotePort 16022二号内网机器用代理名 ssh-host2remotePort 16023外部访问时ssh -p 16022和ssh -p 16023分别连到不同机器。如果代理名重复frps会报错如果remotePort重复端口会冲突。这个在规划阶段就要做好记录机器多了之后很考验命名规范。5.3 安全加固别把SSH裸奔到公网穿透的本质是把内网端口暴露到公网这意味着你的SSH直接面对着全网扫描器。如果不做防护暴力破解是早晚的事。我的建议是至少做以下几件第一frp的token一定要用高强度随机串并且定期轮换。token一旦泄露别人就能利用你的VPS做端口映射这是很危险的事。第二frps面板不要暴露在公网。我已经把webServer.addr设成了127.0.0.1如果你非要外部访问面板建议先SSH登录VPS再通过ssh -L 17500:127.0.0.1:17500做本地端口转发来看而不是直接把17500端口放到公网。第三在frps.toml里限制能够射出的远端端口范围防止某个frpc被攻破后攻击者随意占用VPS端口allowPorts [ { start 16022, end 16039 }, { start 18080, end 18080 } ]第四如果穿透的只是自己日常用的SSH建议改成密钥登录把密码登录关掉。这一步和frp无关但它是你内网机器暴露到公网后的第一道防线。第五考虑用STCP类型替代TCP类型。STCP的逻辑是服务端只做信令握手流量走两个frpc之间的点对点加密通道。也就是说SSH流量并不真正经过VPS中转安全性更高速度也可能更快。代价是你的访问端比如你自己的电脑也得装frpc并配置visitor。如果你能接受这个成本STCP是SSH穿透最好用的方式。5.4 常见进阶玩法把MySQL、RDP或者内网网站都穿透出去frp不只是给SSH用的。我之前就把公司内网的MySQL3306穿透到了公网方便在家用Navicat连数据库排查问题。配置方式和SSH几乎一样[[proxies]] name mysql-internal type tcp localIP 192.168.1.100 localPort 3306 remotePort 13306Windows远程桌面也同理把RDP的3389端口映射到公网remotePort设一个不常用的比如13389本地mstsc连接时填VPS的IP加端口就能远程操作内网Windows机器。如果你有多台内网机器提供不同Web服务还可以结合Nginx做虚拟主机分发。比如frps的vhostHTTPPort设成18080然后VPS上的Nginx监听80端口把不同域名的请求反向代理到127.0.0.1:18080。这样外部访问就能用标准的80端口省去带端口的麻烦多个域名还能各自指向不同内网服务。5.5 监控与日志别等连不上才想起来看frp自带的Dashboard我建议必须开哪怕只是内网访问。它能显示当前有多少个frpc在线、每个代理的连接数、流量占用方便你快速判断是不是有人占满了带宽。服务端日志默认会往标准输出写用journalctl -u frps -f可以实时查看。客户端日志也一样本地机器上journalctl -u frpc -f。遇到问题先看日志远比瞎猜配置高效得多。另外顺手加个每日日志轮转或者定期清理journal保存大小避免VPS的小硬盘被日志塞满。命令很简单sudo journalctl --vacuum-size100M这句可以写进crontab每周跑一次。我在实际部署中用这套方案已经稳定跑了一年多最深的体会是内网穿透并不是一个“装完就忘”的工具它会持续暴露在外网环境下所以从一开始就要把端口规划、安全加固和监控体系做好。最后再分享一个实用小技巧每次改完frp配置先只跑一个最简单的TCP隧道验证链路成功之后再往上叠加其他代理。这样一旦出问题你永远知道是链路的问题还是新加代理的问题排查速度会快很多。