frp内网穿透安全进阶:端口映射、STCP、P2P直连与可观测性 frp 这个东西做内网穿透的人早晚都会碰到。前两篇我把安装、基础映射和几种常见拓扑过了一遍后台收到最多的问题却不是怎么通而是通了之后怎么收场。说实话这才是关键frp 本身只是一根管子真正决定安全边界的是管子两头接着什么。这篇就聊两件事一是把 frp 用歪之后会带来哪些实打实的危害二是在安全前提下把 frp 用深——多服务复用、按域名分发、私密隧道、P2P 直连、负载均衡和可观测性。不管你是刚学会remotePort的新手还是已经在生产环境跑了几十个 proxy 的老手下面这些坑我基本都踩过一遍能帮你少走不少弯路。1. 先把风险摆上台面frp 用错一步内网等于开了扇后门1.1 真正的危害从来不是 frp而是被映射出去的那个端口很多人对 frp 的认知停留在工具层面觉得它就是个端口搬运工出不了大事。这个判断只对了一半。frp 确实不负责业务它只负责把流量从公网搬到内网但问题恰恰出在这里它把原本只有内网能摸到的东西搬到了全互联网都能摸到的地方。你在家里局域网访问127.0.0.1:6379的 Redis风险基本为零一旦这个端口通过 frpc 映射到公网几个小时之内就会有人来敲门。我做过实测一台全新开的云主机只要 6379 对外开放日志里最快的一次是上线后 11 分钟就出现了完整的握手和INFO探测请求。更隐蔽的一层风险在服务端。frps 默认监听 7000它的鉴权模型是拿着 token 的客户端都能映射端口。也就是说token 泄露等于把 frps 的资源分配权交给别人。我在一次内部演练里试过用默认配置起的 frps只要知道 IP 和端口配合弱 token 猜解十几分钟就能挂上自己的 proxy。这时候攻击者用完的不是你的数据而是你的服务器带宽和公网 IP——你成了别人流量的中转站而日志里留下的是你的服务器记录。所以讨论 frp 的危害要分两条线看一条是被映射服务自身的脆弱性一条是frps 控制面的脆弱性。前者决定别人能不能进你的内网后者决定别人能不能借你的服务器干别的事。这两条线任何一条断了后面的深入应用都没意义。1.2 暴露面分级哪些服务映射出去基本等于交钥匙我习惯把要映射的服务按暴露后的后果分级而不是按我需不需要它。因为很多人的判断是反的——越是重要的东西越想着随时能访问结果越容易出事。下面这张表是我自己在用的分级参考你可以对照检查一遍。风险等级典型服务与端口暴露后的直接后果建议做法极高Docker API 2375/2376、K8s API 6443、etcd 2379可直接创建容器并挂载宿主机目录整机权限失守绝对不映射必须远程管理就走 STCP 或跳板机极高Redis 6379、MongoDB 27017、Elasticsearch 9200无鉴权时可读写数据、写计划任务、拖库加密码 只监听内网 走 STCP高SSH 22、RDP 3389、SMB 445弱口令爆破、横向移动、勒索加密改密钥登录优先 STCP公网映射只作备选高群晖 DSM 5000/5001、各类路由器与管理后台面板漏洞直接导致设备接管关闭公网映射改走 STCP 二次验证中内网测试环境、Jenkins、GitLab源码泄露、CI 凭据被读取加访问控制限定 IP尽量挂在非标端口低只读监控面板、静态文件服务信息泄露为主通常不致命仍然要加 Basic Auth不要因为只是看数据就裸奔表里第一行的 Docker API 是我见过最要命的。2375 端口不带任何鉴权一个POST /containers/create请求就能起一个挂载/的容器然后chroot出来就是宿主机 root。很多人在 NAS 上开 Docker 远程管理图方便直接把 2375 扔出去这跟把家门钥匙插在锁上没区别。1.3 四个动作判断自己现在是不是在裸奔不用等出事花二十分钟就能自查一遍。我一般按这个顺序做从外网扫自己。别在内网扫内网扫出来的结果没有意义。用手机流量或者另一台云主机对 frps 的公网 IP 做一次全端口扫描把所有开放端口记下来逐个问自己这个端口为什么开着。我遇到过有人只记得自己映射了 8080扫出来发现还开着 3306原因是半年前测试时留下的配置忘了删。检查 dashboard。frps 的 web 面板默认端口 7500默认账号密码是admin/admin。登录进去能看到所有在线客户端、所有 proxy 名称、本地 IP 和端口——这本身就是一份高质量的资产清单。如果它暴露在公网且没改密码攻击者连扫描都省了。翻 frpc 配置。重点看type tcp的条目里localPort是不是数据库、管理面板或者远程桌面。这类条目应该改成type stcp或者至少加一层访问限制。看服务端日志。frps 日志里会记录客户端上线、proxy 注册、连接来源 IP。翻一遍有没有你不认识的客户端名或者来源 IP 集中的连接。我习惯把日志里的来源 IP 按国家/地区粗分一下出现明显集中的陌生来源就值得警惕。提示只映射你有权管理的设备和服务。拿别人的资产做测试无论出于什么目的都不在讨论范围内。一句题外话把 frp 说成危险工具是冤枉它了。危险的是映射完就不管的习惯。下面几节讲的所有深入玩法前提都是先把上面的暴露面收拾干净。2. 从一条隧道到一套体系多服务复用的三种路由方案2.1 三种方案的本质差异其实就看用什么区分流量新手最常见的做法是给每个服务分配一个remotePort然后用公网IP:端口访问。服务一多就乱了端口记不住、防火墙规则越开越多、HTTP 服务还得自己处理跨域和证书。真正的分水岭在于——你用什么信息把流量分给不同的后端。想清楚这一点方案自然就选出来了。方案区分依据额外依赖适合场景主要缺点TCP/UDP 映射公网端口号无SSH、数据库、游戏、任意四层协议端口占用多无域名语义vhost HTTP请求头里的 Host域名 80 端口Web 后台、API、静态站点只支持 HTTP后端拿不到真实 IP 需额外配置vhost HTTPSTLS 握手里的 SNI域名 443 证书需要 HTTPS 的 Web 服务证书管理麻烦泛域名证书是刚需STCP/XTCPserverName secretKey访问方也要跑 frpc私密服务、不占公网端口访问方门槛高需要分发配置我的选择习惯是Web 类一律走 vhost非 Web 类优先走 STCPSTCP 实在不方便才退回到 TCP。这样公网真正开放的端口只剩下 80、443 和 frps 的 7000防火墙规则能压缩到四条以内后面维护成本会低很多。2.2 subDomainHost 和泛域名解析怎么配才不出错vhost 方案里最容易踩坑的就是域名。服务端的配置项叫subDomainHost它填的是主域不带前面的子域也不带末尾的点。客户端那边两种写法二选一要么用subdomain nas让 frp 自动拼成nas.你的域名要么用customDomains [a.你的域名, b.你的域名]手动指定完整域名。这两种不要混着写同一份 proxy 配置里同时出现subdomain和customDomains行为会变得不太可预期。DNS 那边需要一条泛解析记录*.你的域名指向 frps 的公网 IP。如果只有一台 frps这条就够了。要注意的是泛解析一旦生效任何子域都会打到 frps没匹配上 vhost 的请求会被拒绝返回这在日志里会表现为一堆 404属于正常现象不用慌。还有一个经典冲突两个客户端注册了同一个域名。frp 的处理方式是后者覆盖或者报错具体看版本但无论如何都不是你想要的。如果确实需要多个后端共享一个域名正确做法是用group和groupKey做负载均衡而不是让两个 client 抢。这里有个细节group相同的 proxy 会被视为同一组frps 按某种策略分发请求具体策略在不同版本里行为不完全一致生产环境用之前建议先在测试环境验证一轮。2.3 一份可以直接抄的服务端与客户端配置网上大量教程还是frps.ini的写法从 v0.52 开始官方主推 TOML字段名也换了一批照着旧教程改新版本经常卡在配置文件解析报错上。下面这两份是我目前在用的模板字段都做了注释。服务端frps.tomlbindPort 7000 # 强制客户端也必须启用 TLS防止明文流量被中途观察 transport.tls.force true auth.method token auth.token 换成一段足够长的随机字符串 # 面板只监听本机需要时用 SSH 端口转发进来访问 webServer.addr 127.0.0.1 webServer.port 7500 webServer.user ops webServer.password 另一个强随机口令 webServer.enablePrometheus true # 限制客户端能申请的端口范围避免被拿去做任意端口映射 allowPorts [ { start 20000, end 30000 } ] maxPortsPerClient 5 # HTTP 虚拟主机入口 vhostHTTPPort 80 vhostHTTPSPort 443 subDomainHost 你的域名 log.to console log.level info log.maxDays 7客户端frpc.tomlserverAddr 你的服务器公网IP serverPort 7000 auth.method token auth.token 和服务端保持一致 # 网络质量差可以换成 quicUDP 被限速就用 tcp transport.protocol tcp transport.tls.enable true log.to ./frpc.log log.level info log.maxDays 3 [[proxies]] name nas-panel type http localIP 127.0.0.1 localPort 5000 customDomains [nas.你的域名] transport.useEncryption true [[proxies]] name office-ssh type tcp localIP 127.0.0.1 localPort 22 remotePort 20022 healthCheck.type tcp healthCheck.timeoutSeconds 3 healthCheck.maxFailed 3 healthCheck.intervalSeconds 10几个参数值得单独说明。allowPorts是服务端最后一道闸门就算 token 泄露攻击者也只能在 20000-30000 这个区间里折腾映射不到 22 或者 3306 这种敏感端口配合maxPortsPerClient还能防止单客户端把端口占满。webServer.addr写127.0.0.1之后面板就只能从服务器本机访问需要看的时候用 SSH 隧道把 7500 转发到本地浏览器安全性比改密码高一个量级。transport.tls.force这个开关很多教程都不提但它能避免只有一端开了 TLS、另一端还是明文的尴尬情况。3. 把安全补回来鉴权、加密与访问控制的组合拳3.1 三板斧token、面板、allowPorts这三项我称为 frp 的最低安全配置缺任何一项都算裸奔。token 要足够长别用123456或者项目名。生成方式很简单openssl rand -hex 32一行命令就够了别自己敲键盘拍。token 一旦泄露处理方式和密钥泄露一样立刻更换、重启两端、翻一遍日志确认没有陌生客户端上线过。面板这一项127.0.0.1绑定比强口令更彻底。如果确实需要远程看可以给面板加一层路径前缀或者干脆做成只允许特定来源 IP 的防护。我自己的做法是把面板完全锁死加一个每晚跑的脚本通过curl http://127.0.0.1:7500/api/proxy/tcp拉取当前 proxy 列表跟白名单比对发现差异就给自己发消息。这样既不用暴露面板也能盯着配置有没有被人动过。allowPorts的作用前面说过了这里补一个细节它只约束 TCP/UDP 类型的remotePort不影响 vhost 类型因为 vhost 不占独立端口。所以如果你只用了 vhost 和 STCPallowPorts其实可以留空。但只要有一个type tcp的 proxy就一定要配上。3.2 STCP让只有特定人能访问真正成立STCP 是我认为 frp 最被低估的功能。它的模型是服务端只负责撮合不开放公网端口访问方也要跑一个 frpc用serverName和secretKey找到对应的 proxy然后把流量绑定到本地某个端口。整个过程中公网上没有任何一个端口暴露给这个服务。服务提供方这样写[[proxies]] name private-db type stcp secretKey 一段独立生成的长随机串 localIP 127.0.0.1 localPort 3306 allowUsers [ops-laptop]访问方这样写[[visitors]] name db-visitor type stcp serverName private-db secretKey 和服务提供方完全一致 bindAddr 127.0.0.1 bindPort 13306之后在访问方机器上连127.0.0.1:13306就等于连到了服务提供方的 3306。allowUsers是个可选但强烈建议加的字段它对应访问方 frpc 配置里的user值相当于在 secretKey 之外再加一层身份白名单。secretKey和auth.token必须分开生成不要偷懒复用——一旦某个访问方的机器被翻泄露的也只是这一个服务的密钥换掉它不影响其他服务。3.3 TLS、加密、压缩别为了更安全把 CPU 跑满transport.tls.enable负责的是隧道本身的传输加密防的是流量在路上被观察。transport.useEncryption是在此之上的应用层加密主要用来防止中转节点看到明文内容。两个都开CPU 开销大概是单开 TLS 的 1.5 倍左右我在一台四核小主机上测试过跑满千兆带宽的时候两个都开单个核心占用会到 70% 以上。压缩则要反过来看。transport.useCompression对文本类流量JSON 接口、HTML 页面收益明显我实测一个 200KB 的 JSON 响应能压到 40KB 左右但对已经压缩过的内容图片、视频、压缩包几乎没效果反而白白消耗 CPU。所以我的策略是HTTP 类 proxy 开压缩数据库和文件传输类不开。加密则视内容敏感度决定公网传输的数据库连接建议开只是本地同步用的静态文件服务可以省下这点开销。配置项防的是什么性能代价我的建议transport.tls.enable隧道明文被观察中服务端强制开启客户端跟随transport.useEncryption中转节点看到内容中高数据库、凭据类服务开启transport.useCompression带宽占用过高低到中文本类开二进制类关healthCheck后端挂了仍然转发极低全部开启间隔 10 秒注意加密和压缩都是逐条 proxy 配置的不是全局开关。写配置的时候容易只改了一条其他条忘了改。改完建议用面板或者 API 拉一次完整配置做比对。4. 进阶玩法P2P 直连、负载均衡与健康检查4.1 xtcp 的适用条件以及它为什么经常打洞失败xtcp 的目标很诱人让两端直接建立连接流量不走 frps延迟和带宽都不受服务器限制。但它有个硬前提——至少一端的网络要允许外部主动连进来。家用宽带常见的情况是运营商给的是可预测的端口映射这种环境下打洞成功率很高如果两端都在严格对称的网络后面打洞就会失败。frpc 里的相关配置长这样[[visitors]] name p2p-nas type xtcp serverName p2p-nas-proxy secretKey 独立的长随机串 bindAddr 127.0.0.1 bindPort 18080 keepTunnelOpen true服务端那边就是普通的type xtcpproxy 配置。keepTunnelOpen打开之后frpc 会持续尝试维持隧道省去每次访问都要重新打洞的时间代价是待机时也有少量心跳流量。打洞失败的排查顺序我一般是这样的先看两端 frpc 的日志里有没有 STUN 探测结果能不能拿到自己的公网映射地址再确认中间有没有设备做端口限制最后拿一对已知能直连的网络做对照测试。如果确认环境不支持别硬扛退回 STCP 就好——至少 STCP 是确定可用的只是流量会经过服务器。frpc 里的natHoleStunServer默认指向公共 STUN 服务网络环境特殊时可以自建一个稳定性和可控性都会好一些。4.2 负载均衡与灰度多个后端共享一个入口group和groupKey的组合可以做到多后端共享同一个域名或端口。配置上两个客户端的 proxy 用相同的group和groupKey服务端就会把它们当作一组。这个能力在灰度发布时特别好用老版本和一个新版本各起一个 proxy加进同一个 group观察一段时间再调整比例。不过有两个坑要提醒。第一健康检查要配合着开否则一个后端挂了流量还是会被分过去表现为间歇性的 502。第二有状态服务不要用负载均衡比如带 session 的 Web 后台请求被分到不同后端会导致登录状态反复丢失。这种情况要么改用共享 session要么就老老实实一个后端。4.3 健康检查让挂掉的后端自动被摘掉健康检查的配置很简单但参数取值有讲究。intervalSeconds太短会造成额外压力太长则故障发现不及时我一般取 10 秒。timeoutSeconds取 3 秒比大多数内网服务的响应时间宽松又不至于让故障判断拖太久。maxFailed取 3 次也就是最坏情况下 30 秒左右会把后端摘掉。healthCheck.type分tcp和http两种。tcp只做端口连通性探测够用而且开销小。http可以指定path能真正验证业务是否正常比如探测/healthz而不是只看端口。这里有个细节http类型的探测默认会带上Host头如果后端做了 Host 校验要确保配置里的域名和后端期望的一致否则会出现端口通着但健康检查一直失败的情况。5. 稳定性与可观测性日志、监控和资源限制5.1 日志策略别让它把磁盘写满frp 的日志默认输出到控制台用 systemd 或者 launchd 托管的时候会进系统日志不配置轮转的话几个月就能吃掉几十 G。log.to指定文件路径log.maxDays控制保留天数这两个一定要配。log.level平时用info排查问题时临时调到debug排查完记得调回来——debug模式下每条连接都会打日志量大到能把磁盘写爆。在 Mac 上用 Apple Silicon 的机器跑 frpc可以直接用 Homebrew 装brew install frp brew services start frpHomebrew 的 formula 会同时提供 frpc 和 frps在 M 系列芯片上是原生 arm64 版本不需要额外处理。配置文件默认落在/opt/homebrew/etc/frp/下改完用brew services restart frp生效。这里容易踩的坑是路径Homebrew 装的 frp 读取的配置路径和你手动下载解压后的路径不一样改了配置不生效的时候先确认程序到底读的是哪一份文件。5.2 Prometheus 指标把感觉还行换成数字webServer.enablePrometheus true打开之后frps 会暴露一批运行指标包括当前在线客户端数、各类型 proxy 数量、连接数和流量统计。指标名字里有frp_前缀接进 Prometheus 之后可以配几条最基础的告警在线客户端数突降可能是网络抖动也可能是配置被改单个 proxy 的连接数异常升高可能是在被扫描或者被刷流量在夜间出现持续高位值得看一眼是谁在用这几条我不追求告警精准宁可多几条噪音也不要完全靠感觉。真实情况是很多问题你根本不会主动去看面板只有告警推过来才会知道。5.3 带宽与连接数限制别让一条 proxy 拖垮整台机器frp 支持在客户端侧对单条 proxy 做带宽限制字段是transport.bandwidthLimit配合transport.bandwidthLimitMode可以按发送、接收或者双向限制。这个功能在共享 frps 的场景下很有用——比如家里多个人都在用同一台服务器做穿透给每条 proxy 设个上限避免一个人传大文件把整条链路的可用带宽占干净。连接数方面frps 的maxPoolCount和客户端的poolCount会影响连接复用行为。默认值在大多数场景下够用但如果你的服务有大量短连接可以适度调大池子减少建连开销。调之前建议先看指标确认瓶颈真的在连接建立上而不是盲目加大——池子不是越大越好空闲连接本身也占资源。6. 常见故障排查实录与避坑清单下面这张表是我这几年反复遇到、也反复被问到的几类问题。列出来方便你按现象直接定位不用每次都从日志头翻到尾。现象常见原因处理方式frpc 连不上 frps服务端 7000 未放行token 不一致服务端版本不兼容先用telnet IP 7000确认链路再对比两端 token 和版本号域名访问返回 404subDomainHost填错DNS 泛解析未生效域名和customDomains不匹配检查解析结果用curl -H Host: x直连 frps 验证连接间歇性失败多后端未开健康检查后端本身不稳定打开健康检查先确认后端独立访问是否稳定面板打不开webServer.addr绑定了 127.0.0.1通过 SSH 端口转发访问或临时改绑定地址后立刻改回上传大文件中断带宽限制触发传输协议不适配当前网络检查bandwidthLimit尝试切换transport.protocolxtcp 一直连不上网络环境不支持打洞查看日志中的打洞结果退回 STCP映射的端口被外部扫描服务本身无鉴权立即下线该映射或改用 STCP配置文件改了不生效程序读取的路径与编辑的文件不一致用ps查看进程启动参数确认-c指向的文件再补几条不成体系的实操心得都是我踩过坑之后才记住的第一改配置前先备份。frp 的配置一旦语法错误进程会直接起不来线上服务瞬间断掉。我现在习惯在改之前先cp frps.toml frps.toml.$(date %F)出问题直接回滚比在日志里找语法错误快得多。第二把端口映射当变更来管理。每加一条 proxy都在一个文档里记一行谁申请的、什么时候加的、什么时候可以删。我见过太多临时的映射挂了两年最后没人记得它为什么存在。第三健康检查后端的探测路径别写成需要登录的页面。探测一个需要 session 的页面永远返回 302健康检查会一直失败然后你会以为是 frp 的问题。健康检查路径要选那种不依赖登录、只表达进程活着的接口。第四别用生产服务器同时跑 frps 和别的对外服务。frps 会占用 7000 端口和大量连接句柄混在一起出问题时排查范围会扩大好几倍。有条件的单独开一台小机器专门做中转成本不高但省心很多。第五定期做一次断联演练。把 frps 停掉看看哪些服务会受影响、有没有备用访问路径。这个动作每季度做一次能暴露出很多平时看不见的依赖。我在实际使用中最深的一个体会是frp 的价值不在能通而在可控地通。刚上手的时候谁会去关心 token 长度、面板绑定地址这些细节都是能连上就完事。真正让人睡不着觉的是某天凌晨收到一条你的服务器在对外发起异常连接的告警然后你发现有个半年前的测试映射一直开着。所以我现在的习惯是任何一条要留在公网上的映射都要能回答三个问题这个服务本身有没有鉴权、这个端口有没有暴露必要、出事了怎么在两分钟内关掉它。三个问题里有一个答不上来就先别加。