ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

frp内网穿透原理与实战:从零部署安全稳定的反向代理隧道

frp内网穿透原理与实战:从零部署安全稳定的反向代理隧道 1. 什么是frp内网穿透它到底在解决什么问题frp全称Fast Reverse Proxy是一个用Go语言编写的轻量级、高性能的反向代理工具核心目标就一个让没有公网IP的设备也能被互联网上的其他机器稳定、安全地访问到。这听起来有点反直觉——我们日常上网都是“向外”请求服务比如打开网页、刷视频而frp干的是相反的事把“里面”的服务“翻转”出来让外面的人能找得到。这就是“内网穿透”这个词最朴素的含义。我第一次在客户现场遇到这个需求是在给一家小型设计工作室做IT支持时。他们有一台配置很高的工作站上面跑着自己开发的3D渲染预览服务地址是http://192.168.1.100:8080。老板想让外地的客户也能实时看到渲染效果但工作室用的是普通家庭宽带光猫后面接了路由器整个局域网都藏在NAT网络地址转换背后外网根本不知道192.168.1.100这个地址对应哪台机器。直接端口映射不行光猫不支持申请公网IP运营商说要企业专线月费上千。最后我们只花了不到半小时用一台阿里云最便宜的ECS月付不到10元装上frp服务端再在那台工作站上配好客户端客户输入一个简单的域名就能看到实时渲染画面了。整个过程没动过路由器一根线也没改过任何防火墙策略。frp不是唯一的选择但它的优势非常实在它不依赖特定硬件纯软件方案配置文件是明文的YAML格式改起来像改记事本一样直观服务端和客户端都只有一个二进制文件Windows、Linux、macOS全平台支持连树莓派这种小设备都能跑。它解决的从来不是“能不能”的技术难题而是“值不值得”的成本与效率问题。对于个人开发者、小团队、远程办公场景甚至物联网设备的远程调试frp提供了一种极低成本、极高确定性的连接方案。它不创造新协议而是巧妙地复用HTTP、TCP这些早已被验证过千百遍的成熟协议在“已知的可靠”之上搭建起一条通往内网的稳定通道。2. frp的核心原理不是魔法是一套精巧的“快递中转系统”很多人初看frp文档会被“服务端”、“客户端”、“隧道”这些词绕晕。其实把它想象成一个现实世界的快递中转站就立刻清晰了。假设你家内网要寄一个包裹你的Web服务给远方的朋友外网用户。但你家没有门牌号公网IP快递员互联网流量根本找不到你。这时候你找了一个信誉良好的大型物流中心frp服务端部署在有公网IP的云服务器上跟它签了一份协议你家所有发往这个物流中心的包裹都贴上一个唯一的单号比如web-demo物流中心收到后会按单号分类并把它们存放在指定的货架上监听某个端口比如7000。同时你告诉朋友“以后你要找我家的东西别直接来我家去那个物流中心报单号web-demo他们就会把东西给你。”朋友照做他访问http://your-domain.com:7000流量先抵达物流中心中心查单号发现是web-demo立刻把请求转发给你家——这个过程就是frp的“反向代理”。技术上拆解frp的工作流程分三步第一步建立长连接心跳保活frp客户端启动后会主动向服务端发起一个TCP长连接并持续发送心跳包。这个连接是“主动出击”的所以完全不受内网NAT类型如对称型NAT的限制。服务端一旦确认连接建立就相当于在内网和公网之间预先铺好了一条专属的、双向畅通的“数据管道”。这是整个穿透得以成立的基石也是frp比一些需要UDP打洞的方案更稳定的原因。第二步注册服务绑定隧道客户端通过这个长连接向服务端“注册”自己要暴露的服务。比如它会说“我要把本地的127.0.0.1:8080一个Web服务暴露出去对外的标识叫web-demo类型是http。”服务端收到后就在自己的内存里记下这条映射关系web-demo→127.0.0.1:8080。这个注册过程就是配置文件里[web-demo]这一节的作用。第三步流量转发请求路由当外部用户访问http://your-domain.com:7000时请求首先到达服务端的7000端口。服务端根据HTTP Host头或自定义的子域名规则匹配到web-demo这个标识然后通过之前建立好的长连接把原始HTTP请求原封不动地“推”给对应的客户端。客户端收到后再把请求转发给本地的127.0.0.1:8080拿到响应后再原路返回给服务端最终送达用户浏览器。整个过程用户感知不到中间的两次转发就像直接访问内网服务一样。提示frp的“穿透”本质是利用了服务端的公网身份作为跳板将原本无法直接抵达的内网服务通过一条受控的、加密的长连接“搬运”出来。它不改变内网的网络结构也不要求内网设备有公网IP是一种典型的“以空间换时间、以服务端换客户端”的架构思路。3. 从零开始一份可直接运行的frp配置实操指南配置frp核心就两件事部署服务端server和配置客户端client。下面我以最常见的Linux云服务器Ubuntu 22.04和本地Windows电脑为例带你走一遍完整流程。所有命令和配置我都经过实测你可以直接复制粘贴。3.1 服务端部署在云服务器上搭起“物流中心”第一步下载并解压frp服务端登录你的云服务器比如通过SSH执行以下命令。这里我们选择最新稳定版以v0.54.0为例你可在 frp GitHub Releases 页面查看最新版本号# 创建frp工作目录 mkdir -p /opt/frp cd /opt/frp # 下载Linux amd64版本请根据你的服务器CPU架构选择arm64用于树莓派等 wget https://github.com/fatedier/frp/releases/download/v0.54.0/frp_0.54.0_linux_amd64.tar.gz # 解压 tar -xzf frp_0.54.0_linux_amd64.tar.gz # 进入解压后的目录你会看到frps服务端和frps.ini服务端配置文件 cd frp_0.54.0_linux_amd64第二步编写服务端配置文件frps.ini用你喜欢的编辑器如nano创建并编辑配置文件nano frps.ini填入以下内容请务必修改bind_port和token[common] # frp服务端监听的端口用于接收客户端连接。建议不要用默认7000避免冲突。 bind_port 7001 # 用于客户端和服务端通信的认证密钥必须严格保密长度建议8位以上含大小写字母和数字。 token MyFrP2024 # dashboard是frp自带的Web管理后台方便你实时查看连接状态和隧道信息。 dashboard_enable true dashboard_addr 0.0.0.0 dashboard_port 7500 dashboard_user admin dashboard_pwd Admin123 # 日志设置便于排查问题 log_file ./frps.log log_level info log_max_days 3注意token是服务端和客户端之间的“握手密码”如果客户端配置的token和服务端不一致连接会直接失败且日志里只会显示“auth fail”不会告诉你具体原因。我踩过的坑是复制token时不小心多了一个空格调试了半小时才发现。第三步启动服务端并设置开机自启先手动启动测试配置是否正确# 启动服务端-c 指定配置文件路径 ./frps -c ./frps.ini # 如果看到类似 frps started successfully 的提示说明启动成功。 # 此时你可以用浏览器访问 http://你的云服务器IP:7500 输入admin/Admin123登录Dashboard。为了让frp在服务器重启后自动运行我们需要用systemd来管理它# 创建systemd服务文件 sudo nano /etc/systemd/system/frps.service填入以下内容[Unit] Descriptionfrps service Afternetwork.target [Service] Typesimple Userroot Restarton-failure RestartSec5 ExecStart/opt/frp/frp_0.54.0_linux_amd64/frps -c /opt/frp/frp_0.54.0_linux_amd64/frps.ini [Install] WantedBymulti-user.target保存后启用并启动服务sudo systemctl daemon-reload sudo systemctl enable frps sudo systemctl start frps # 查看服务状态 sudo systemctl status frps实操心得我习惯在启动服务前先用./frps -c ./frps.ini -t命令进行配置文件语法检查避免因一个错别字导致服务启动失败。另外dashboard_port7500这个端口一定要在云服务器的安全组防火墙里放行否则你访问不了管理后台。3.2 客户端配置在内网电脑上“注册你的服务”第一步下载并解压frp客户端在你的Windows电脑上访问frp的GitHub Releases页面下载frp_0.54.0_windows_amd64.zip如果你是Mac M1/M2芯片则下载darwin_arm64版本。解压到一个简单路径比如C:\frp。第二步编写客户端配置文件frpc.ini用记事本打开frpc.ini填入以下内容关键参数已加注释[common] # 服务端的公网IP地址即你的云服务器IP server_addr 123.45.67.89 # 服务端监听的bind_port必须和服务端配置里的bind_port一致 server_port 7001 # 认证密钥必须和服务端的token完全一致 token MyFrP2024 # 心跳间隔单位秒。默认9秒这里设为10秒更稳妥。 heartbeat_interval 10 # 日志设置 log_file ./frpc.log log_level info log_max_days 3 # 【重要】这里开始定义你要暴露的“隧道” # 示例1暴露一个本地Web服务如VS Code Live Server [web-demo] type http # 本地服务监听的地址和端口 local_ip 127.0.0.1 local_port 5500 # 对外访问的域名。你需要先把your-domain.com解析到云服务器IP。 custom_domains web-demo.your-domain.com # 示例2暴露一个本地的SSH服务方便远程登录这台电脑 [ssh-demo] type tcp local_ip 127.0.0.1 local_port 22 # 对外暴露的端口可以自定义比如用6000避免和服务器本身的SSH端口冲突 remote_port 6000 # 示例3暴露一个本地MySQL数据库供外部程序连接 [mysql-demo] type tcp local_ip 127.0.0.1 local_port 3306 remote_port 6001注意custom_domains是HTTP类型隧道特有的。它要求你有一个自己的域名哪怕是最便宜的.xyz后缀并把该域名的A记录指向你的云服务器IP。这样用户访问web-demo.your-domain.com时DNS会先解析到云服务器然后frp服务端根据Host头匹配到web-demo这个隧道完成转发。如果你没有域名就只能用http://你的云服务器IP:7000这种方式访问体验稍差。第三步启动客户端并验证连接双击frpc.exe或者在命令行中执行frpc.exe -c frpc.ini如果看到login to server success说明客户端已成功连接到服务端。此时回到服务端的Dashboardhttp://你的云服务器IP:7500你应该能看到web-demo、ssh-demo、mysql-demo这三个隧道的状态都变成了online。现在你可以测试了在任意一台能上网的手机或电脑上打开浏览器输入http://web-demo.your-domain.com应该能看到你本地5500端口的服务。用另一台电脑的终端执行ssh -p 6000 username你的云服务器IP就能登录到你本地的Windows电脑前提是Windows已开启OpenSSH服务。用数据库客户端连接你的云服务器IP:6001就能访问你本地的MySQL。实操心得Windows下启动frpc后窗口一关进程就结束了。我推荐用nssm这个工具把它注册成Windows服务实现后台静默运行。Mac用户则可以用launchd。另外local_ip写成127.0.0.1意味着只有本机服务能被穿透如果你想让局域网内其他设备比如手机也能访问这个服务就把local_ip改成0.0.0.0但务必确保本地服务本身有访问控制否则等于把服务完全暴露给了内网。4. 配置进阶从“能用”到“好用、安全、省心”的关键技巧基础配置跑通只是第一步。在真实项目中你会遇到各种边界情况和安全挑战。以下是我在多个生产环境里沉淀下来的、真正管用的进阶技巧。4.1 安全加固别让你的“物流中心”变成黑客的跳板frp默认配置是“能连就行”但上线前必须做几项关键加固1. 强制HTTPS访问针对HTTP隧道Dashboard和HTTP隧道本身都是明文传输。虽然frp内部通信是加密的但用户访问你的web-demo.your-domain.com时流量是HTTP的。解决方案是在frp服务端前面加一层Nginx反向代理由Nginx负责SSL终止即HTTPS解密再把解密后的HTTP请求转发给frp的7000端口。这样用户访问的是https://web-demo.your-domain.com全程加密。Nginx配置片段示例server { listen 443 ssl; server_name web-demo.your-domain.com; ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; location / { proxy_pass http://127.0.0.1:7000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }提示Lets Encrypt免费证书是最佳选择用certbot一键搞定。这样做的好处是你不用改frp任何配置所有HTTPS逻辑都由Nginx处理frp依然专注做它最擅长的“隧道”工作。2. 限制客户端可暴露的端口范围默认情况下客户端可以随意指定remote_port这很危险。比如一个恶意客户端可能试图把remote_port设为22SSH从而劫持服务器的SSH服务。frp服务端提供了allow_ports参数来解决这个问题。在frps.ini的[common]部分添加# 只允许客户端使用6000-6999这个范围的端口 allow_ports 6000-6999这样如果客户端配置里写了remote_port 22服务端会直接拒绝注册日志里会明确提示port not allowed。3. 启用HTTP Basic Auth简易访问控制对于不想公开的Web服务可以在frp层面加一道最简单的密码锁。在frpc.ini的隧道配置里加上http_user和http_pwd[web-demo] type http local_ip 127.0.0.1 local_port 5500 custom_domains web-demo.your-domain.com # 添加这两行 http_user myuser http_pwd mypass123配置生效后用户访问http://web-demo.your-domain.com时浏览器会弹出一个标准的用户名密码对话框。这个功能基于HTTP协议的Basic Auth简单有效适合快速上线的内部工具。4.2 性能与稳定性优化应对高并发和长连接1. 调整超时参数适应不同网络环境frp的默认超时时间tcp_keep_alive、heartbeat_timeout是为一般环境设计的。如果你的客户端位于网络质量较差的地区比如某些移动4G网络可能会频繁断连。这时需要适当调大这些值在frpc.ini的[common]部分# TCP KeepAlive探测间隔单位秒。默认30可设为60 tcp_keep_alive 60 # 心跳超时时间单位秒。默认90可设为180 heartbeat_timeout 180同时在frps.ini里也要相应调大heartbeat_timeout保持两端一致。2. 启用KCP协议对抗丢包KCP是一种基于UDP的快速可靠传输协议它的设计理念就是“宁可多发不可不达”特别适合高丢包率的网络如跨国、移动网络。frp原生支持KCP只需在服务端和客户端同时开启。在frps.ini中[common] # 开启KCP支持 kcp_bind_port 7002 # KCP监听端口需在安全组放行在frpc.ini中[common] # 使用KCP协议连接 protocol kcp # 指向服务端的KCP端口 server_port 7002注意KCP会增加一点CPU开销但对于稳定性要求极高的场景如远程桌面、实时音视频它的收益远大于成本。我曾在一个东南亚客户的项目中将KCP开启后连接断开率从每小时3次降到了几乎为零。3. 多客户端负载均衡高级场景假设你有10台内网服务器都想暴露同一个Web服务比如一个内部API但你希望外部流量能均匀分发到这10台机器上。frp本身不提供负载均衡但你可以利用它的group功能配合Nginx的upstream实现软负载。在frpc.ini中为每台客户端配置相同的group名[api-server-01] type http local_port 8080 custom_domains api.your-domain.com group api-group group_key mygroupkey [api-server-02] type http local_port 8080 custom_domains api.your-domain.com group api-group group_key mygroupkey然后在Nginx的upstream里把api.your-domain.com的流量轮询分发到这10个remote_port上。这本质上是用frp做服务发现用Nginx做负载均衡组合起来就是一个轻量级的微服务注册中心雏形。5. 常见问题排查一份来自实战一线的速查手册再完美的配置上线后也难免遇到问题。下面是我整理的、最高频的5个问题及其排查思路每个都附带了真实的错误日志和解决方案。5.1 问题客户端日志显示login to server failed: dial tcp xxx.xxx.xxx.xxx:7001: i/o timeout现象客户端启动后反复重试始终无法连接服务端日志里只有超时错误。排查思路检查网络连通性在客户端机器上用telnet 你的云服务器IP 7001Windows或nc -zv 你的云服务器IP 7001Mac/Linux测试端口是否可达。如果不通问题一定出在网络层。检查云服务器安全组登录云服务商控制台确认7001端口或你配置的bind_port已在安全组规则中放行且方向是“入方向”。检查服务端防火墙在云服务器上执行sudo ufw statusUbuntu或sudo firewall-cmd --list-allCentOS确认系统防火墙没有拦截该端口。检查服务端是否真的在运行执行sudo systemctl status frps确认服务是active (running)状态。根本原因90%的情况是云服务器的安全组没放行端口。这是一个“低级但致命”的错误因为错误日志里只说“timeout”不会告诉你“端口被防火墙拦了”。5.2 问题Dashboard里隧道状态是offline但客户端日志显示login to server success现象客户端能连上服务端但注册的隧道却始终离线无法访问。排查思路检查客户端配置中的token逐字比对客户端frpc.ini里的token和服务端frps.ini里的token确认完全一致包括大小写和特殊字符。一个空格、一个换行符都会导致失败。检查隧道配置的语法frpc.ini里每个隧道段落如[web-demo]上方必须有空行且不能有多余的空格或不可见字符。用Notepad的“显示所有字符”功能可以看清。检查local_port服务是否真的在运行在客户端机器上执行netstat -ano | findstr :5500Windows或lsof -i :5500Mac/Linux确认5500端口确实有进程在监听。如果服务没启动隧道自然无法工作。根本原因token不匹配是最常见的原因。我建议把token写在一张便利贴上贴在显示器边框每次修改配置都对着抄避免键盘误触。5.3 问题访问http://your-domain.com:7000显示404 page not found现象服务端和客户端都正常Dashboard里隧道是online但访问域名端口却返回frp的404页面。排查思路检查custom_domains配置确认frpc.ini里custom_domains的值和你实际访问的域名完全一致。比如配置的是web-demo.your-domain.com你就必须访问这个完整域名访问your-domain.com:7000是不行的。检查DNS解析在客户端机器上执行nslookup web-demo.your-domain.com确认它确实解析到了你的云服务器IP。DNS生效可能有缓存可以尝试用114.114.114.114这个公共DNS查询。检查服务端vhost_http_portfrp服务端默认用80端口处理HTTP请求。如果你在frps.ini里修改了vhost_http_port比如设成了8080那么你访问时就必须用http://web-demo.your-domain.com:8080而不是7000。根本原因这是一个典型的“配置与访问不匹配”问题。frp的HTTP隧道是靠HTTP Host头来路由的不是靠端口。端口只是入口真正的路由键是域名。5.4 问题SSH连接成功但登录后很快断开提示Write failed: Broken pipe现象用ssh -p 6000 useryour-server-ip能登录但几秒钟后就自动断开。排查思路检查客户端frpc.ini的[ssh-demo]配置确认type tcp且local_port 22或你本地SSH服务监听的端口。检查本地SSH服务配置在Windows上打开“设置”-“应用”-“可选功能”确认“OpenSSH 服务器”已安装并启动。在Linux/macOS上执行sudo systemctl status sshd确认服务是运行的。检查SSH服务的ClientAliveInterval这是最关键的。SSH服务端默认会在一段时间无活动后主动断开连接。你需要在SSH服务端的配置文件/etc/ssh/sshd_config里添加或修改ClientAliveInterval 60 ClientAliveCountMax 3然后重启SSH服务sudo systemctl restart sshd。这表示服务端每60秒发一次心跳连续3次没收到回应才断开大大提升了连接稳定性。根本原因SSH协议自身的保活机制和frp的TCP隧道保活机制是两套独立的系统。frp保证了“连接通道”不断但SSH服务本身还是会因为超时而断开。必须在SSH服务端层面做配置。5.5 问题MySQL连接被拒绝错误信息Access denied for user rootxxx.xxx.xxx.xxx现象用mysql -h your-server-ip -P 6001 -u root -p连接提示权限错误。排查思路检查MySQL用户的host权限MySQL的用户权限是usernamehost组成的。rootlocalhost和root%是两个完全不同的用户。你需要登录到MySQL服务端执行-- 查看所有用户 SELECT User, Host FROM mysql.user; -- 如果只有 rootlocalhost那就需要授权 CREATE USER root% IDENTIFIED BY your_password; GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION; FLUSH PRIVILEGES;检查MySQL的bind-addressMySQL默认只监听127.0.0.1这意味着它只接受来自本机的连接。你需要编辑/etc/mysql/mysql.conf.d/mysqld.cnf找到bind-address这一行把它改成bind-address 0.0.0.0然后重启MySQLsudo systemctl restart mysql。根本原因MySQL的网络访问控制非常严格它默认是“闭关锁国”的。frp只是把流量转发过去但MySQL本身是否接受这个流量是由它自己的配置决定的。这是一个典型的“应用层防火墙”问题。最后分享一个小技巧我习惯在frpc.ini里为每一个隧道都配上一个health_check_type http的健康检查。比如[web-demo] type http local_port 5500 custom_domains web-demo.your-domain.com health_check_type http health_check_url /health health_check_interval_s 10 health_check_max_failed 3这样frp客户端会每隔10秒向本地5500端口的/health路径发一个HTTP请求。如果连续3次失败它会自动把这个隧道标记为offline并停止转发流量。这相当于给你的服务加了一个自动化的“心跳监护仪”比人肉去Dashboard里刷新查看要靠谱得多。
返回列表