拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

frp内网穿透SSH:无公网IP也能轻松远程连接内网服务器

frp内网穿透SSH:无公网IP也能轻松远程连接内网服务器

说实话,第一次在无公网IP的网络里把SSH“救”出来的时候,我就知道frp内网穿透这工具得专门写一篇。事情是这样的:实验室一台服务器,配置不错,跑着训练任务和一堆脚本,但它在校园网里,只有内网IP。中午回宿舍想上去看一眼日志,不行;出差在高铁上想改个参数,更不行。后来折腾了一圈frp内网穿透SSH,终于实现了随时随地远程访问内网服务器,体验直接拉满。这篇文章就把我从0到1搭起来的完整过程、踩过的坑和最终稳定运行的方案都写出来,适合任何手里有内网服务器、又想在外面通过SSH安全连回去的开发者、运维和折腾党。

1. 为什么需要frp内网穿透:先说清楚要解决什么问题

1.1 没有公网IP的困境

绝大多数日常环境里的服务器,是不直接暴露在公网上的。校园网、家庭宽带、公司办公网,基本都处于NAT后面,设备拿到的都是类似192.168.x.x、10.x.x.x这样的私有地址。这个地址只在当前局域网内有效,到了外面,别说访问了,连路由都找不到你。

有人会说,路由器上不是有“端口映射”和“DMZ主机”功能吗?我直接把内网服务器的22端口映射到路由器WAN口上,不就能从外面SSH了吗?理论上是这样,但有个硬前提:你的宽带必须拿到公网IPv4地址。现在很多运营商给你的是大内网地址,光猫和路由器都是层层NAT,你连WAN口真实IP都不是公网的,端口映射做了也白做。就算运气好有公网IP,还要面对动态IP变化、运营商封禁80/443/22端口、光猫管理员权限拿不到等一系列问题。我折腾了好几天,最终给这条思路判了死刑:想靠传统端口映射解决,要么有条件要么看运气,不够通用。

那怎么办?思路还得反过来想:既然外面的设备连不进内网,那就让内网的设备主动“走出去”,跟一台有公网IP的服务器保持连接,再由这台公网服务器做中转,把外部SSH请求转发回内网。这个思路,就是内网穿透,而frp就是最常用的自托管实现方案之一。

1.2 为什么选frp而不是其他方案

当时我其实也对比过好几条路线,各有各的问题,简单复盘一下:

方案优点缺点我的取舍
路由器端口映射配置简单,无额外软件需要公网IP,受运营商限制多环境不具备,弃用
TeamViewer / 向日葵远程桌面免配置,开箱即用图形界面重,不适合纯命令行场景,自动化能力弱不适合脚本和终端操作,弃用
ngrok有免费版,部署快免费版域名随机、连接数有限制,自托管配置稍复杂可以临时用,稳定性一般
樱花内网穿透有免费隧道,适合临时演示免费线路带宽小,节点不稳定作为备选可以,不适合生产
frp开源、自托管、支持TCP/HTTP/HTTPS/stcp等多种协议,社区活跃需要一台有公网IP的服务器最终选择

我选frp的核心原因有三点:第一,数据链路完全掌握在自己手里,不依赖第三方中转服务的稳定性;第二,它是Go语言写的单二进制文件,部署极轻量,内网服务器上扔一个文件就能跑;第三,配置灵活,我不仅能穿透SSH,后面想顺带穿透Grafana、Prometheus的Web界面、甚至暴露一个HTTP服务,改几行配置就能做到。对于一个长期要用的基础设施来说,这种可控性很重要。

2. frp穿透SSH的整体架构与核心原理

2.1 架构拆解

先看整体上有几个角色参与。一套最简的frp穿透SSH系统,由三部分组成:

  • 内网服务器:就是你想要远程访问的那台机器,上面运行着SSH服务(默认22端口),同时运行frp的客户端程序frpc。
  • 公网服务器:一台有公网IP的云服务器,运行frp的服务端程序frps。它负责接收来自外部的SSH连接请求,然后通过已经建立好的隧道转发给内网服务器。
  • 访问端设备:你手里的笔记本、手机或工作站,上面只有一个普通的SSH客户端,比如OpenSSH、Termius、FinalShell等。

连接路径是这样的:

访问端SSH客户端 → 公网服务器IP:6000端口 → frps转发 → frpc接收 → 内网服务器22端口

我在配置里约定,公网服务器上用6000端口对外暴露SSH服务。也就是说,我在外面执行ssh -p 6000 user@公网IP,请求先落到frps上,frps一看这个端口对应着一条名为“ssh”的隧道,就把它顺着frpc已经维持住的连接传回内网服务器,内网服务器上的SSH服务收到后正常处理认证和会话。整个过程对SSH协议来说是透明的,SSH客户端和服务端都感知不到中间经过了层层转发。

2.2 核心原理通俗解释

frp能工作的关键在于“反向”二字。传统正向代理是客户端主动去连服务端,而内网服务器在NAT后面,外部无法主动连它。frp的做法是:让内网服务器的frpc主动向外连接公网服务器的frps。这个连接一旦建立,frps就获得了一条可以“回拨”内网服务器的通道。之后外部访问公网服务器端口时,frps通过这条已经存在的通道把数据“塞”回去,frpc再转给本地的SSH服务。

打个比方:你把内网服务器想象成一个藏在办公室深处工位上的人,外部SSH用户是来访的客户。客户不知道这个人在哪,直接打他分机打不通。frps是前台总机,frpc是这个人办公桌上的一根内线电话。这根内线电话是由这个人自己先拨通到前台总机的,一直保持着通话状态。客户来了,先打前台总机(公网服务器6000端口),前台说“稍等,我转给张三”,然后通过那根一直保持的内线把客户的语音接过去,张三就能和客户直接对话了。这里“保持通话的内线”就是frpc与frps之间的控制连接和工作连接。

具体到frp内部,它把连接分为两类:

  • 控制连接:frpc启动后主动与frps建立的长连接,用于传递心跳、隧道配置同步、状态上报等控制信息。
  • 工作连接:当外部请求真正到来时,frps和frpc基于控制连接协商出的数据通道,用来搬运实际的TCP流量。

业务数据不占用控制连接,两者分离的好处是:即使某条数据流被断开,frpc和frps之间也能维持心跳,尽快发现异常并重连,不会因为一次SSH会话退出就把整个隧道搞挂。

3. 环境准备与部署实操

3.1 需要准备什么

在动手之前,先把材料备齐。这套方案只需要三类东西:

第一,一台有公网IP的服务器。我自己用的是云服务器,操作系统选的是Debian 12,1核2G的配置就足够跑frps,因为frps本身非常轻量,内存占用常年不到50MB。操作系统用Ubuntu、CentOS、Debian都行,差别只在systemd配置的一两个命令上。

第二,内网服务器。就是你想远程访问的那台机器,操作系统同样是Linux最佳。前提是它已经开启了SSH服务,并且你确认在内网里用ssh user@内网IP能正常连上。

第三,frp的安装包。frp在GitHub上有官方release,根据系统架构选择对应文件。我的两台机器都是x86_64,所以下载的都是frp_x.x.x_linux_amd64.tar.gz。如果你的内网服务器是树莓派或者ARM开发板,记得选arm64版本。这里特别提醒:frp的版本迭代比较快,配置文件格式在0.52版本之后全面转向了TOML格式,和旧版的INI格式不兼容。我下面写的配置都是基于新版TOML语法,你去查资料时如果看到[common]、server_addr这种写法,说明那是老版本,别直接混用。

下载完压缩包,解压后目录里会有两个核心可执行文件:frps和frpc,分别对应服务端和客户端。目录里还有对应的frps.toml和frpc.toml示例配置,可以先看一眼,但更多还是按我的精简配置来。

3.2 frps服务端配置

先把frps部署到公网服务器上。我把解压后的frps和frps.toml放在/opt/frp/目录下,方便管理。一份最简配置长这样:

bindPort = 7000 auth.method = "token" auth.token = "your-strong-random-token" webServer.addr = "0.0.0.0" webServer.port = 7500 webServer.user = "admin" webServer.password = "your-dashboard-password"

bindPort是frps监听frpc连接的端口,也就是内网服务器要主动连过来的“前台总机号码”。这个端口不对外提供SSH服务,只用于frp自身通信。我习惯用一个较冷门的端口,避免被大量扫描流量骚扰,比如7022、17000这类。

auth.method = "token"加上auth.token是服务端和客户端之间的认证凭证。一定要设置,并且设置成足够随机的长字符串。不要小看这一步,frp的通信端口如果暴露在公网且没有认证,可能被陌生人用来搭非法隧道,风险很大。我生成token用的命令是:

openssl rand -hex 16

输出一串32位的十六进制随机值,直接填进去就行。

后面四行配置的是frp自带的Dashboard监控面板。开启后,浏览器访问http://公网IP:7500,输入用户名密码,就能看到当前所有隧道的在线状态、流量统计、连接数等。这个面板在生产环境非常有用,排查问题第一件事就是看这里,后面我会讲到。

配置写好后,先手动启动验证:

cd /opt/frp ./frps -c frps.toml

看到日志输出frps started successfully,说明服务端已经起来了。这时按Ctrl+C停掉,我们把它注册成systemd服务,让它在后台常驻并开机自启。

创建/etc/systemd/system/frps.service:

[Unit] Description=frp server service After=network.target [Service] Type=simple ExecStart=/opt/frp/frps -c /opt/frp/frps.toml Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target

然后执行:

systemctl daemon-reload systemctl enable frps systemctl start frps systemctl status frps

看到active (running)就对了。这里我把Restart=on-failure加上,即使进程意外挂掉,systemd会在5秒后拉起来,减少人工干预。

3.3 frpc客户端配置

服务端就绪,接下来配置内网服务器上的frpc。同样在/opt/frp/放好frpc和frpc.toml。客户端配置如下:

serverAddr = "你的公网服务器IP" serverPort = 7000 auth.method = "token" auth.token = "your-strong-random-token" [[proxies]] name = "ssh" type = "tcp" localIP = "127.0.0.1" localPort = 22 remotePort = 6000

前两行指定frps的地址和端口,让frpc知道该往哪里连。认证部分和服务端保持一致,token一定要完全一样,否则连接会被拒绝。

[[proxies]]这一段是隧道的核心配置。name = "ssh"是这条隧道的名字,会在frps的Dashboard里显示,最好起一个直白的名字。type = "tcp"表示这是一条TCP隧道,SSH、数据库、RDP这类基于TCP的协议都可以用它。localIP和localPort指明内网服务器上真实提供服务的位置,SSH就在本机22端口,所以写127.0.0.1和22。remotePort是公网服务器上对外暴露的端口,这里我填6000,意味着外部设备通过公网IP:6000来连接内网的SSH服务。

这里要提醒一点:remotePort不要和frps的bindPort重复,也不要占用云服务器上其他服务的端口。我习惯预留一段端口专门给frp用,比如6000到6010,后续想穿透多个服务,就在[[proxies]]里继续加。比如我要再穿透内网服务器上的Grafana,就再加一段,name = "grafana"、localPort = 3000、remotePort = 6010,外部访问公网IP:6010就能打开Grafana页面。

同样地,给frpc加systemd服务:

[Unit] Description=frp client service After=network.target [Service] Type=simple ExecStart=/opt/frp/frpc -c /opt/frp/frpc.toml Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

注意我在这里用的Restart=always,和服务端略有不同。因为内网网络状况复杂,断网、休眠、路由器重启都可能导致frpc连接中断,设置为always能最大化保证断线自动恢复。

启动后,登录frps的Dashboard,看到ssh这条隧道状态是“online”,就说明两端握手成功了。

4. 远程SSH连接验证与使用

4.1 连接测试

隧道状态正常,现在做端到端验证。任意一台能上公网的设备上,执行:

ssh -p 6000 username@你的公网服务器IP

这里的username是内网服务器上的用户名,不是公网服务器的。因为我转发的是内网服务器的22端口,所以认证过程完全由内网服务器的SSH服务负责,你用哪个用户连,它就看哪个用户。输入密码,如果看到内网服务器的shell提示符,那就成了。

连上之后,我建议顺手做两个小优化。

第一个,配置SSH的ServerAliveInterval参数。因为流量经过frp中转,长时间不发数据时,某些中间网络设备可能把空闲连接回收掉,表现就是SSH会话莫名卡住或断开。在/etc/ssh/ssh_config或者你自己的~/.ssh/config里加上:

Host mylab HostName 你的公网服务器IP Port 6000 User username ServerAliveInterval 30 ServerAliveCountMax 3

之后只需要执行ssh mylab就能直接连上,不需要每次都带端口和用户名。ServerAliveInterval 30的意思是每30秒发一次心跳包,维持连接活跃。

第二个,上传文件到内网服务器,直接用SCP,端口和SSH一致:

scp -P 6000 ./backup.tar.gz username@你的公网服务器IP:/home/username/

注意SCP的端口参数是大写-P,小写-p是保留文件时间属性的参数,很多人第一次写容易踩。

4.2 生产环境关键建议

隧道能通只是第一步,真要长时间用,安全加固这关过不了迟早出事。下面这几项是我在部署后陆续补上的,强烈建议照做。

第一,SSH侧安全配置。内网服务器上修改/etc/ssh/sshd_config:

  • PermitRootLogin no:禁止root直接远程登录,日常操作走普通用户,需要提权再sudo。
  • PasswordAuthentication yes如果暂时只靠密码,先保留;但长期建议切换为密钥认证,把PasswordAuthentication改成no。
  • 如果担心暴力破解,可以安装fail2ban,监控SSH日志,连续失败多次就临时封禁来源IP。

第二,frp公网侧端口选择。我用的是6000,建议你选一个1024以上的高位端口,别用22、2222这种人人都知道扫的端口。安全性靠的是token和SSH本身,但减少被扫描器盯上的概率能省很多麻烦。

第三,frpc所在目录权限收紧。运行frpc的用户最好不是root,可以单独建一个frp用户来跑服务:

useradd -r -s /usr/sbin/nologin frp chown -R frp:frp /opt/frp

然后在systemd服务的[Service]段里加一行:

User=frp

这样即使frpc被利用,权限也受限。

第四,配置frps与frpc的TLS加密。frp支持传输层加密,在服务端和客户端的配置里分别加一行:

transport.tls.enable = true

直接写明enable = true即可。如果在frps上启用了TLS,frpc上也必须对应开启,否则握手不成功。开启后,即使流量被中途截获,也不容易直接看到明文。

5. 常见问题与排查技巧实录

5.1 问题速查表

这一路下来,我把遇到过的和同行交流中典型的问题整理成一个速查表,按“症状 -> 原因 -> 处理方式”的顺序看:

症状常见原因处理方式
frpc日志出现connect to server failed: connection refusedfrps没启动,或serverAddr/serverPort写错了,或云服务器防火墙/安全组没放行7000端口在公网服务器上检查systemctl status frps;用`ss -lntp
frpc日志出现login to server failed: EOFtoken不匹配,或frps和frpc版本差异过大对比两端配置里的auth.token,保持frp版本一致
外部ssh -p 6000提示Connection refusedfrps的remotePort对应隧道未建立,或安全组没放行6000端口先在Dashboard里看隧道是否online;再检查公网服务器安全组是否放行了6000端口入方向
能连上但SSH提示密码错误或拒绝认证认证走的是内网服务器的SSH,和frp无关在内网本机用ssh user@127.0.0.1测试,确认用户密码正确;检查sshd_config是否允许该用户登录
隧道状态正常但速度很慢公网服务器带宽成为瓶颈查看frps所在服务器的带宽上限;SSH传输大量数据时尽量用压缩模式ssh -C
frpc连接经常自动断开网络不稳定,路由器空闲连接回收确保systemd配置Restart=always;SSH侧设置ServerAliveInterval;必要时在frpc配置里调低transport.heartbeatInterval和transport.heartbeatTimeout

5.2 几个我踩过的坑

问题表是按现象列的,但有几个坑属于“现象不明显、排查靠经验”的类型,单独拿出来说。

第一个坑:云服务器安全组和系统防火墙双重拦截。我最初在腾讯云上把安全组端口放开了,结果发现frpc还是连不上,折腾半天才发现云服务器自带的firewalld还开着,默认没有放行7000端口。以后排查顺序应该是:先看进程有没有起来,再看系统防火墙,最后看云安全组。每一步都有命令可验证,别凭感觉。

第二个坑:frp版本跨代导致的配置格式问题。我刚上手时拿的是最新版frp,但网上大量教程还是旧版INI写法。旧版frpc.ini长这样:

[common] server_addr = x.x.x.x server_port = 7000

新版改用TOML之后,标识符从下划线变成了驼峰式,比如serverAddr、bindPort。如果直接把旧配置文件给新程序用,frp会直接报错或者忽略配置。我的建议是:要么用最新版并遵循TOML语法,要么锁定一个熟悉的稳定版本,不要混。

第三个坑:frpc和frps版本不一致导致握手失败。有一阵我在公网服务器上更新了frps,忽略了内网服务器上还是老frpc。结果控制连接能建立,但传输数据时频繁断开,Dashboard里看到的状态时好时坏。后来统一两端版本,问题秒没。养成习惯:升级时服务端和客户端同步升级。

第四个坑:别忘了remotePort和本地SSH端口是两码事。我有一次在配置文件里把localPort和remotePort都写成了22,结果公网服务器上22端口根本不是我的(云服务器自己有SSH服务),把系统自带的SSH顶掉的风险不说,访问也全部异常。正确理解:公网端口只是一个“门牌号”,你可以在6000上开一扇门,里面的服务仍然是22。

6. 把frp变成一套可持续使用的远程访问基础设施

隧道搭好、坑也排完,最后分享一点我把它当“基础设施”来用的经验。

我用的frpc配置里不止一条隧道,而是把常用服务都规划进了同一套体系。除了SSH,我还穿透了内网Grafana的3000端口、Prometheus的9090端口,以及在需要调试时临时暴露的某个内部API。这样我在外面只需要记住一个公网IP和几个端口号,就能访问到整个内网环境。Dashboard上的流量统计也能看到哪条隧道被频繁使用,很有参考价值。

安全上,我实践下来比较好用的是“访问端白名单”思路。如果只有你自己的IP需要访问,可以在云服务器安全组里只放行这个IP访问那个remotePort,把暴露面降到最低。但如果你和我一样,经常在不同网络环境切换IP,这个办法就不太灵活了,我是靠SSH密钥加fail2ban来兜底,目前运行大半年,日志里没有一次成功入侵的迹象。

另外,frp本身会定期检查更新,但别一看到新版本就急着升。我的习惯是:大版本更新先在测试机上验证配置兼容性,确认没问题再换到生产机。frp这种网络基础设施,稳定压倒一切。

这套方案我从踩坑到通用化,前后花了差不多两个下午。现在出差在酒店、在高铁上,甚至用手机Termius连一下服务器看个训练曲线,都是几秒钟的事。清楚SSH是怎么从内网“走出去”的,后续再加什么服务、调什么参数,心里都有底。希望我这篇frp内网穿透SSH的完整记录,能让你少走几步弯路。

返回列表