尧图精选

frp+nginx组合,打造统一入口的内网穿透方案

🕒 发布时间:2026/10/1 4:52:16 📁 来源:尧图网络
如果你手里有一台公网机器办公室或者家里又跑着好几个内网服务出门在外想随时访问它们frp这个名字你应该不陌生。frp是目前社区里使用最广的内网穿透工具之一一条命令就能把内网服务“送”到公网上去。但很多人搭完frp之后会很快遇到一个新问题隧道是通了访问入口却很乱——不同服务要记不同端口想加个域名、挂个HTTPS证书还得顺着frp自己的配置逻辑去折腾时间一长维护成本就上来了。这时候nginx就该登场了。这个项目的本质是把frp和nginx串成一套完整方案frp负责把内网流量搬到公网入口nginx负责在这个入口统一做反向代理、域名分发、HTTPS卸载和访问控制。用上这套组合以后内网服务对公网来说就只剩下一两个干净入口80和443。做到这一步远程办公、临时给客户演示内网系统、在外面连回自家设备的管理页面体验都会舒服非常多。这篇内容适合谁看如果你正在用frp但觉得入口太乱或者刚接触内网穿透想知道除了“开一堆端口”之外有没有更优雅的做法那这份配置思路可以照抄。我会把frps、frpc以及nginx的关键配置逐段拆开讲最后附上我自己踩坑记录的排查清单。1. 项目整体思路frp和nginx各自负责什么1.1 frp的边界它只负责把隧道打通很多人对frp有一个误解觉得装上frp之后内网服务就能“自动”以很优雅的方式暴露出去。实际上frp做的事情非常专注它只是一个隧道程序。你在内网跑一个frpc客户端在公网服务器上跑一个frps服务端两边建立长连接之后公网服务器的某个端口就和内网服务的某个端口连在了一起。外部访问公网服务器的这个端口流量就会顺着隧道到达内网。frp自己当然也提供了一些“入口整理”能力。比如配置里的vhostHTTPPort可以让你在内网定义多个HTTP类型的代理每个代理绑定一个自定义域名frps根据HTTP请求里的Host头自动把请求路由到对应的内网服务。这个功能在小规模场景下确实够用但一旦你的需求变成“统一日志”、“统一HTTPS证书”、“限制来源IP”、“做请求频率控制”只靠frp就会很别扭。frp毕竟不是Web服务器它不做请求头改写、不做SSL会话复用、也没有upstream负载均衡那些生态里常见的功能。所以我的理解是frp解决的是“流量怎么过去”至于“流量到了公网之后怎么被漂亮地接管”那是反向代理该干的事。1.2 nginx要做的三件事域名、HTTPS、访问控制把nginx放在frp的公网入口前面是我在实际项目里验证过最顺手的一种做法。它在这个架构里承担三件明确的事。第一域名收敛。不管内网有多少个服务公网入口就是一台服务器的一个IP。nginx监听80和443通过server_name区分你访问的是哪个域名再把对应请求转发给frps暴露出来的端口。这样用户只需要记住类似app.example.com、blog.example.com这样的地址完全不用关心端口号。第二HTTPS统一卸载。内网服务大多数跑在HTTP上如果每个服务都自己配证书、自己维护HTTPS麻烦不说还不一定都支持。nginx在入口处把443端口的HTTPS流量解密然后以HTTP协议转发给frp。对内网服务来说它眼里只有普通的HTTP请求不需要做任何改动。证书只在nginx这一层管理维护成本瞬间降下来。第三访问控制。frp本身有token认证但它只能认证“客户端和服务器”之间没法精细到“每个请求的来源IP”。nginx在入口层就方便多了限制某条路径只允许公司出口IP访问或者给管理页面套一个Basic Auth都是几行配置的事。frp不用动内网服务不用动入口处一层搞定。想清楚这三件事后面的配置就有方向了。2. 部署形态与前置准备2.1 两种常见架构nginx放在公网侧还是内网侧先说架构不然配置写出来容易对不上号。我看过很多教程很多人对“frp反向代理nginx”的理解其实不一样实际操作起来有两条路线。第一种nginx放在公网入口侧。公网服务器上同时跑着frps和nginxnginx监听80/443frps监听一个高位端口比如8080作为vhostHTTPPort。外网请求先到nginxnginx按域名把请求转发给本机的frps端口frps顺着隧道把请求送到内网服务。这条路线适合“你有多个内网Web服务想在公网统一暴露、统一域名管理”的场景也是本文重点讲的一条。第二种nginx放在内网侧。公网服务器上只跑frps内网有一台nginx作为统一入口frpc把内网nginx的80端口通过TCP隧道暴露到公网。外部访问frps暴露的公网端口流量直接被送到内网nginx然后再由内网nginx分发到后面的多个服务。这条路线适合“内网已经有成熟的nginx反向代理结构只是想把这个结构原封不动搬到公网来用”的场景。这两条路线并不冲突甚至可以在一个项目里同时使用公网nginx负责域名的第一层分发frp负责把流量送到内网内网如果还有多个服务再让内网nginx做第二层分发。理解了这两层关系后面任何配置都不会看迷糊。2.2 环境清单服务器、域名、端口规划在动手之前先列一下我常用的环境清单避免配置到一半发现缺东西。公网侧需要一台有公网IP的云服务器Linux系统我用的是Debian系CentOS/RHEL系换一下包管理器命令就行。frp官方对系统没有特别要求Go编译的单二进制文件随便扔到/usr/local/bin就能跑。域名我先准备了一个主域名比如example.com然后给每个要暴露的内网服务加一个子域名解析记录统统指向这台公网服务器的IP。端口规划是这套配置里最容易踩坑的地方。我的习惯是SSH用22nginx用80和443frps的bindPort用7000frps的vhostHTTPPort用8080。之所以frps不用80是因为80要让给nginx。如果frps和nginx都在同一台公网服务器上80端口冲突是必然的把vhostHTTPPort放到8080是最稳妥的做法。内网侧需要一台装了frpc的机器它能访问到你要暴露的服务就行不一定非要跑在服务本身那台机器上。比如内网有多台开发机我一般会在其中一台常开的机器上放frpc通过不同的代理定义指向不同IP的内网服务。3. frp全链路配置从frps到frpc3.1 frps服务端frps.toml关键参数frp从0.52版本之后配置文件的格式从原来的ini变成了toml两个版本的字段名有所调整。如果你是从老教程抄的frps.ini直接套到新版上会报错这一点先注意一下。下面的配置我以新版toml为准。我的frps.toml长这样bindPort 7000 auth.method token auth.token 换成你自己的随机字符串 transport.tls.force true vhostHTTPPort 8080 webServer.addr 127.0.0.1 webServer.port 7500 webServer.user admin webServer.password 换成强密码 allowPorts [ { start 8001, end 8010 } ]逐项解释一下。bindPort7000是frpc连上来用的端口相当于frps的主入口默认就是7000保持默认即可。auth.token是客户端和服务端之间的共享密钥这个一定要改而且是整套配置里最不能偷懒的地方。我用openssl rand -hex 16生成一个随机串然后两边填一样的值。transport.tls.forcetrue表示frps和frpc之间的流量强制走TLS加密。frp隧道里跑的是你的内网业务数据在公网链路上裸奔不太合适把TLS打开之后即使有人在网络中间抓包看到的也是加密流量。这个参数看需求但对于涉及内部系统访问的场景我建议打开。vhostHTTPPort8080就是前面说的HTTP虚拟主机端口。frpc配置HTTP类型代理时外网流量会通过这个端口进入frpsfrps再根据Host头把请求分发到对应的内网服务。因为nginx已经监听了80和443这里必须避开。webServer是frp自带的Dashboard可以查看连接状态和流量统计。注意我只让它监听127.0.0.1不让公网直接访问。想看监控的时候我在这台服务器上开一个SSH隧道或者通过nginx加一条路径反代到这个7500端口安全性高很多。allowPorts用来限制frpc可以申请的远程端口范围。如果不写这个客户端只要知道token就有权申请公网服务器上的任意端口这就等于把服务器端口管理权交出去了。限制到8010以内配合nginx和frps自身需要的几个固定端口整体可控很多。3.2 frpc客户端http和tcp两种代理写法frpc的配置是整套方案里最灵活的部分核心是理解[[proxies]]数组里不同代理类型的作用。我常用的有两种http类型和tcp类型。先看http类型适合暴露HTTP/HTTPS的Web服务serverAddr your-server-public-ip serverPort 7000 auth.method token auth.token 和frps保持一致 transport.tls.enable true [[proxies]] name internal-web type http localIP 127.0.0.1 localPort 80 customDomains [app.example.com]http类型不需要占用frps服务器的其他远程端口它共用vhostHTTPPort8080这个入口。frps收到HTTP请求后根据请求头里的Host字段匹配customDomains里面配置的域名然后把请求转发给对应的内网服务。所以customDomains里的域名解析到公网服务器IP之后访问它就能直接到达内网服务的80端口。再来看tcp类型适合数据库、SSH、以及非HTTP协议的服务[[proxies]] name internal-ssh type tcp localIP 192.168.1.20 localPort 22 remotePort 8001tcp类型需要指定remotePortfrps会在公网服务器上监听这个端口把流量转发到内网的22端口。好处是啥协议都能传坏处是每多一个服务就要占一个公网端口。如果暴露的是Web服务优先考虑http类型这样端口能复用nginx那层也好统一管理。我这里刻意把http和tcp分开写就是想强调一点frp的代理类型不是随便选的。能用http类型的服务就尽量别用tcp否则你的公网服务器会慢慢积累一长串监听端口和“统一入口”的初衷就背道而驰了。3.3 用systemd托管frp进程并设置开机自启frp进程如果只是手动启动一旦断线、机器重启就得手动拉起来完全不实用。建议直接注册成systemd服务。frps这边的service文件/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.targetfrpc这边的service文件同理把ExecStart改成/usr/local/bin/frpc -c /etc/frp/frpc.toml就行。写完执行sudo systemctl daemon-reload sudo systemctl enable --now frps内网机器上执行sudo systemctl daemon-reload sudo systemctl enable --now frpc启用之后用systemctl status frpc确认状态是active (running)。如果发现进程反复重启十有八九是配置文件写错了先journalctl -u frpc -n 50看日志别急着怀疑网络问题。4. nginx反向代理接入把入口收敛到80和4434.1 基础接法nginx反代frp的vhostHTTPPortfrp隧道搭好之后如果你访问http://app.example.com:8080会发现能打开内网服务。这就算通了但带着:8080访问实在太难看了也不利于后面上HTTPS。现在让nginx把这个端口“藏起来”。nginx这边新建一个server配置按域名把请求转发到frps的vhostHTTPPortserver { listen 80; server_name app.example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }关键在于proxy_pass指向的是本机8080也就是frps的vhostHTTPPort。一个容易忽略的细节是proxy_set_header Host $host这行必须保留。frps是靠Host头来识别应该把请求路由到哪个customDomains的如果你把Host头覆盖成127.0.0.1:8080frps就找不到对应的代理了要么404要么直接路由错乱。X-Real-IP和X-Forwarded-For则是把客户端的真实IP透传下去。内网服务如果做了访问日志或者业务上需要拿到用户IP这两个头能帮上大忙。我最初漏配了这两个头结果内网日志里全是127.0.0.1排查问题的时候非常被动。配置改完用nginx -t检查语法然后systemctl reload nginx。之后直接访问http://app.example.com就不再需要写端口了。4.2 多服务分发一个入口接管所有内网Web如果你内网有不止一个Web服务这套方案的威力就出来了。多配几个[[proxies]]每个用不同的customDomains然后在nginx里按域名配对应server块即可。举个例子。我有一条frpc配置暴露了三个服务[[proxies]] name web-app type http localIP 127.0.0.1 localPort 8081 customDomains [app.example.com] [[proxies]] name web-blog type http localIP 127.0.0.1 localPort 8082 customDomains [blog.example.com] [[proxies]] name web-nas type http localIP 192.168.1.30 localPort 5000 customDomains [nas.example.com]nginx这边就写三个server块每个的server_name对应一个子域名proxy_pass全部指向127.0.0.1:8080。外部用户访问blog.example.com时nginx把这个请求交给frps的8080端口frps一看Host头是blog.example.com立刻匹配到内网那台机器的8082端口一个来回请求就到了正确的服务上。整套流程里公网服务器永远只监听80和443内网不管有多少台机器、多少个服务入口只有一个。新增服务时我只需要在frpc配置里加一条[[proxies]]再在nginx里加一个server块reload两边服务即可完全不影响其他业务。4.3 加HTTPS证书配置与自签名方案域名分发搞定之后下一步就是上HTTPS。现在浏览器对HTTP站点越来越不友好很多浏览器特性比如摄像头、麦克风权限都要求HTTPS环境才能用这几个内网服务如果要在正式场景里使用证书是绕不开的。有公网域名且域名解析正常的情况下我首选certbot自动申请Lets Encrypt证书sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d app.example.com -d blog.example.comcertbot会自动帮你改nginx配置、续期证书省心。注意前提是80端口能正常访问到你的nginx服务证书申请时要校验域名所有权。内网环境或者域名没法自动申请证书的时候可以用自签名证书顶一下。生成私钥的方式openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/example.key \ -out /etc/nginx/ssl/example.crt \ -subj /CNapp.example.comnginx配置里挂上证书server { listen 443 ssl; server_name app.example.com; ssl_certificate /etc/nginx/ssl/example.crt; ssl_certificate_key /etc/nginx/ssl/example.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }再加一个80端口的server块把HTTP流量301跳到HTTPSserver { listen 80; server_name app.example.com; return 301 https://$host$request_uri; }自签名证书浏览器会提示不安全正式使用体验不太好但用于自己远程调试足够了。如果后面内网服务迁移到正式环境把证书换成正规CA的即可nginx配置不需要大改。5. 进阶实操Mac M4客户端、负载均衡与WebSocket5.1 Mac M4上跑frpc的小记最近很多人的日常工作机换了Mac M4在Apple Silicon上跑frpc非常省事。frp官方release里有darwin-arm64的预编译包下载解压就能直接运行。如果你想用Homebrew管理也可以直接装brew install frp装好之后配置文件和Linux端写法完全一致serverAddr your-server-public-ip serverPort 7000 auth.method token auth.token 和frps保持一致 [[proxies]] name mac-local type http localIP 127.0.0.1 localPort 3000 customDomains [dev.example.com]在Mac上跑frpc有一点值得提醒macOS对未签名程序的网络权限把控比较严格首次运行时如果macOS弹窗提示“无法验证开发者”去系统设置-隐私与安全性里选择“仍要打开”即可。另外如果用Homebrew安装的frp后续更新直接brew upgrade frp比自己管理二进制舒服很多。如果你希望Mac开机自动启动frpc可以在launchd里挂一个plist。不过我个人实测下来毕竟常用机器不是24小时开机真到了需要长期稳定暴露服务的时候还是用小主机或者老笔记本当常驻frpc节点更靠谱。Mac上跑frpc更适合临时要把当前开发环境暴露出来给同事看一眼的场景。5.2 内网多实例负载均衡frp多隧道nginx upstream如果内网同一个服务部署了多台实例想在公网入口做负载均衡可以复用nginx的upstream能力。frpc这边把每台内网实例用不同的tcp类型代理暴露出来分别占用不同的远程端口[[proxies]] name svc-node-1 type tcp localIP 192.168.1.11 localPort 9000 remotePort 8011 [[proxies]] name svc-node-2 type tcp localIP 192.168.1.12 localPort 9000 remotePort 8012nginx这边定义一个upstream包含这两个端口upstream internal_svc { server 127.0.0.1:8011; server 127.0.0.1:8012; } server { listen 80; server_name svc.example.com; location / { proxy_pass http://internal_svc; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这样外部请求打到svc.example.com时nginx会把负载分给frps的两个远程端口再由各自的frp隧道送到对应的内网实例。等于用frp的tcp隧道做了一组“远端转发端口池”nginx在这个池子前面做负载均衡。这个方案比较适合内网服务本身无状态、可以直接水平扩展的场景。5.3 WebSocket和长连接场景的nginx参数内网服务如果有WebSocket或者Server-Sent Events这类的长连接nginx默认配置可能会把连接扼杀在超时上。frp隧道本身能保持长连接但在frp前面的nginx需要把连接升级头和超时参数配好。WebSocket的关键配置如下location /ws/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }proxy_http_version 1.1是必须的HTTP/1.0不支持Upgrade头。proxy_set_header Upgrade和Connection upgrade让nginx把WebSocket的握手请求原样透传给frps。proxy_read_timeout和proxy_send_timeout则根据业务需要调整3600秒是1小时适合需要保持长连接的在线页面。没有配Upgrade头之前我这边WebSocket总是握手成功之后没多久就断开。加完这几个参数连接就稳定了。如果你的服务虽然没有WebSocket但页面里有轮询接口proxy_read_timeout也不宜设置过短不然请求稍微慢一点就会被nginx掐断表现为偶尔的504。6. 常见问题排查实录6.1 流量进不来端口、安全组、路由三层排查frp总体配置不复杂但现在找我排查问题的人里有一半是卡在“通不了”上面。我把排查顺序固定成三层先看端口再看安全组最后看路由。第一层端口监听。在公网服务器上执行ss -lntp | grep -E 7000|8080|80|443确认frps和nginx都监听了对应端口。这一步能快速排除“frps没启动”、“端口被别的进程占用了”这类问题。第二层云厂商安全组。云服务器厂商的安全组规则和服务器内部防火墙是两套东西。我之前遇到过一例服务器里ss查出frps正常监听7000但外网就是连不上最后发现是安全组没放行7000端口。这个错最隐蔽因为你服务器怎么查都正常。第三层路由和日志。frpc连不上frps时在frpc机器上执行nc -vz your-server-public-ip 7000能通再查frpc日志journalctl -u frpc -n 50看到login to server success说明连接建立成功如果一直login to server failed重点检查token是否一致、frps是否正常运行。6.2 nginx 502和frp路由错乱内网服务能通过http://域名:8080访问但通过nginx的80端口访问就502这是最常见的nginx接入问题。502的本质是nginx找不到可以作为后端的服务放在这个架构里就是nginx把请求转发到了frps的8080但frps没有对应的后端代理可以处理。排查思路是这样先在公网服务器上本地测一下frps的虚拟主机端口是否正常curl -H Host: app.example.com http://127.0.0.1:8080如果这条命令返回了内网服务的页面说明frp链路本身没问题问题出在nginx侧。这时检查nginx配置里proxy_pass是否指向了正确的frps端口以及proxy_set_header Host是否保留了原域名。如果返回502或404问题大概率出在frpc的customDomains配置上。比如frpc里配置的是app.example.com但你访问的是app.example.com.cnHost头匹配不上frps自然不知道往哪里送。这类问题在日志里会有明确提示在frps机器上执行journalctl -u frps -n 50就能看到类似“host not found”的记录一目了然。6.3 安全加固token、仪表盘与访问白名单frp把内网服务暴露到公网之后等于把一部分内网资源放到了公网可达范围安全这块不能偷懒。我基于自己踩过的坑整理了三项必须做的事。第一token强度。auth.token不要用123456、admin这类弱口令建议直接用openssl rand -hex 16生成随机串。frp没有内置暴力破解防护token一旦弱等于把内网入口的钥匙挂在了门框上。第二Dashboard不暴露公网。frps的Dashboard能查看连接历史、流量统计属于敏感信息。配置里强制让它监听127.0.0.1需要访问时通过SSH隧道或者nginx加一层访问认证再反代不要图省事直接监听0.0.0.0。第三nginx入口加访问白名单。有的服务只有你在用完全可以限制来源IP。nginx配置里加一段location /admin/ { allow 203.0.113.1; deny all; proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; }在allow里写上公司出口IP或者你的家庭宽带公网IP其他来源一律拒绝。这样即使域名被人扫到也进不了管理路径。另外定期看一下frps和nginx的访问日志。日志里如果出现大量不同IP对同一路径的扫描说明有人盯上了这个入口尽快把白名单策略补上。最后分享两个小技巧这套frp加nginx的组合我在自己的服务器上稳定跑了很久。最常用的场景是出差时打开手机浏览器直接访问家里内网的管理页面或者在公司把本机的开发环境通过frpc临时暴露给远程同事review。nginx接管入口之后最大的感受是访问路径永远只有一个443端口剩下全是按域名自动分发。最后分享两个我实际操作中发现的小技巧。第一frpc配置改了之后不用重启整个进程新版支持frpc reload -c frpc.toml可以热加载代理配置对线上业务的影响为零。第二如果你有多个内网服务共用一个frpc实例http类型和tcp类型代理可以混合写在同一个配置文件里不冲突但注意customDomains不要重复否则frps会报域名冲突。这两个细节虽然不起眼但在日常维护里真的能省不少事。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →