资讯动态

Ubuntu SSH反向代理实战:从内网穿透到远程访问

发布时间:2026/10/3 13:13:39 来源:尧图企业网站定制
去年我帮一个朋友调远程办公环境他家里的Ubuntu服务器上跑着几个服务人在公司用vscode连不上折腾了各种内网穿透工具之后最后我用一条最简单的SSH反向代理给解决了。这事儿让我印象挺深——很多玩Linux的朋友对SSH的理解还停留在用它连服务器执行命令完全没意识到它本身就是一个现成的、加密的隧道工具。今天这篇就系统聊聊Ubuntu下怎么用SSH反向代理做远程访问从原理到落地到踩坑一次说透。1. 为什么我会选择SSH反向代理而不是内网穿透工具1.1 一个真实场景家里服务器的远程访问困局先说清楚SSH反向代理解决的是什么问题。假设你人在公司想访问家里那台Ubuntu服务器上的Web服务、数据库或者直接用vscode连上去写代码但家里宽带没有公网IP或者运营商把80和443端口封了这时候你就会陷入一个经典困局机器在线服务在跑但你就是够不着它。大多数人的第一反应是上frp、ngrok这类内网穿透工具。我之前也用过frp确实功能强大但有个问题你躲不掉你得有一台有公网IP的服务器做中转还得在那台服务器上部署frps服务端在内网机器上部署frpc客户端配置两边端口映射维护两套配置和进程。一旦服务端或者客户端的版本不匹配或者配置写错一个字段排查起来相当费劲。ngrok倒是省心但它把流量经过第三方服务器数据安全和稳定性你都控制不了。相比之下SSH反向代理的思路朴素得多既然SSH协议天生就支持端口转发而且你的Ubuntu服务器上必然装了OpenSSH那么只需要一台有公网IP的机器什么额外软件都不用装就能把内网服务的端口反向暴露到这台公网机器上。关键它全程走SSH加密通道数据是加密的而且连接建立之后所有流量看起来就是一条普通的SSH连接稳定性和安全性都有保障。1.2 SSH反向代理和frp/ngrok的本质区别拿frp来对比一下你就明白了。frp的工作方式是内网机器主动连到frp服务端两者之间建立一条长连接然后把内网端口和公网端口做映射。它的设计目标是做通用的内网穿透支持TCP、UDP、HTTP各种协议甚至可以给每个穿透服务配独立的域名和HTTPS证书。SSH反向代理做的事情本质上也是内网机器主动向外发起连接但它走的不是自定义协议而是标准的SSH协议。这意味着不需要额外部署服务端公网机器上只要sshd在运行就能用传输全程加密不像某些穿透工具默认明文传输配置简单到只有一条命令而且SSH本身的参数体系你已经很熟悉了天然支持多组转发一条连接可以同时映射多个端口当然SSH反向代理也有短板比如高并发大流量的场景性能不如专门的穿透工具比如处理UDP协议比较麻烦SSH隧道对UDP支持很弱。所以我说要看场景如果你只是远程SSH进去操作、连个Web管理页面、用vscode写代码那SSH反向代理是性价比最高的方案如果你想在家里跑一个高流量的Web服务给公网用户访问那还是老老实实上frp加nginx。1.3 适用边界什么场景该用、什么场景别用按我实际使用的经验下面这些场景SSH反向代理完全够用远程ssh连接到内网机器远程访问内网Web管理界面路由器、NAS、监控后台vscode Remote-SSH连内网服务器开发临时开放某个内部服务给另一台机器调用数据库管理工具如DBeaver连内网数据库而下面这些场景我建议你用别的方案对外提供正式的Web服务流量大、要求稳定和负载均衡——用nginx反代加frp或者直接用云服务器需要穿透UDP协议比如内网游戏联机——用frp或者ZeroTier这类组网工具需要多人同时使用、权限管理严格的穿透——用frp加token认证或者直接上企业级的组网方案说白了SSH反向代理适合临时、轻量、加密、够用的远程访问需求它是你工具箱里应该常备的一把螺丝刀但不是万能扳手。2. SSH反向代理的核心原理与参数拆解2.1 -R参数到底做了什么SSH反向代理的核心就是-R参数。打开终端你只要执行这么一条命令ssh -R 8080:localhost:3000 useryour-public-server这条命令的意思是在your-public-server这台公网服务器的8080端口上开一个监听所有发到这个端口的数据都会通过当前这条SSH连接被转发到你本地执行命令的这台机器的3000端口上。你可以这样理解你家里的Ubuntu服务器主动向公网服务器喊了一句喂我在你那边挂了个牌子谁访问你的8080端口你就把请求递给我处理。公网服务器干的就是一个二传手的活它本身不保存任何业务数据只是把流量转交给你。为什么叫反向因为正常的SSH是把你的本地请求转发到远端比如-L参数是本地转发你在本地访问一个端口流量被送到远端服务器。而-R是反过来的流量从远端端口进送到本地来。这个方向上的差异恰好让我们能在没有公网IP的情况下让内网机器主动打通一条通向外界的路。2.2 GatewayPorts决定外部能否访问的关键开关这可能是整个SSH反向代理里最容易踩坑的一个点。执行上面那条-R命令后你可能会发现一个问题在本机公网服务器上访问localhost:8080是可以的但从你自己的电脑上访问公网IP:8080却死活不通。原因在于sshd默认配置里有一项GatewayPorts默认值是no。它的含义是反向代理监听的端口默认只绑定在回环地址127.0.0.1上也就是说只有公网服务器本机自己能访问外部机器根本连不到。很安全的默认值但如果你真要远程访问就得把它打开。修改方法编辑公网服务器上的/etc/ssh/sshd_configsudo vim /etc/ssh/sshd_config找到或者添加这一行GatewayPorts yes然后重启sshd服务sudo systemctl restart sshd改成yes之后反向代理的端口就会绑定到0.0.0.0上所有网卡都能访问。这里顺便提一句如果你用的是云服务器还要在安全组里放行对应端口比如8080不然即使系统层面开了云厂商的防火墙也会把你拦住。注意GatewayPorts yes会让所有能访问这台公网服务器的用户都有机会连到你内网机器的端口所以一定要配合密钥认证、白名单这些手段一起用安全细节我在第5节专门讲。2.3 本地端口和远端端口的映射关系理解-R参数里两个端口的关系我建议你把它当成一个快递驿站来看远端端口第一个端口比如8080是驿站门口收货的柜台所有指向它的包裹都先集中在这里本地端口第二个端口比如localhost:3000是你家里的收货地址驿站会把包裹运到你家这两个端口可以完全不同映射关系完全由-R的写法决定。还可以同时做多个映射比如ssh -R 8080:localhost:3000 -R 2222:localhost:22 useryour-public-server这条命令同时保留了Web服务8080→3000和SSH服务2222→22的两条通道。连接建立之后你在任何地方通过公网服务器的8080端口就能访问家里服务器上的Web服务通过2222端口就能ssh连进家里服务器。注意一个细节localhost是相对于执行命令的那台机器而言的也就是你家那台Ubuntu服务器。如果内网里有两台机器A机器想通过公网服务器访问B机器的某个端口可以把命令里的localhost:3000换成192.168.1.100:3000这样转发目标就变成了内网的另一台机器。3. 从零搭建一条稳定的反向代理通道3.1 环境准备与SSH密钥认证讲了这么多原理现在动手搭一条真正能用的通道。整条链路上需要三样东西一台有公网IP的服务器后面叫public-box装Ubuntu或者CentOS都行只要ssh能用一台内网Ubuntu服务器后面叫home-box这是你真正想访问的目标你自己的笔记本或者台式机后面叫my-pc用于最终访问第一步在home-box上生成SSH密钥对让它可以免密登录到public-box。这一步不是可选项而是必须项因为后面我们要用autossh和systemd做自动重连如果连接还需要交互输入密码整个自动化就会卡住。在home-box上执行ssh-keygen -t ed25519 -C home-box-autossh一路回车生成密钥对。然后把公钥拷贝到public-box上ssh-copy-id userpublic-box-ip这一步会要求你输入一次public-box的SSH密码输入之后公钥就装好了。验证一下ssh userpublic-box-ip如果不需要输入密码就能登录说明密钥认证已经生效。这里我推荐用ed25519而不是传统的rsa原因是ed25519的密钥更短、生成更快、安全性也更高。如果你的公网服务器是台老机器或者SSH版本比较旧那就用rsa比如ssh-keygen -t rsa -b 4096。3.2 用autossh保住连接不中断直接跑ssh -R命令做反向代理有一个麻烦SSH连接一旦因为网络抖动、服务器重启、NAT超时等原因断开隧道就断了你得手动重新执行命令。有时候你在外地家里断了一次网整个远程通道就废了非常坑。autossh就是专门解决这个问题的工具。它启动一个SSH进程同时用额外的端口定时探测连接是否还活着断了就自动重连。在Ubuntu上安装很简单sudo apt-get install autossh然后就可以这样启动反向代理autossh -M 0 -N -R 8080:localhost:3000 userpublic-box-ip这里的参数解释一下-M 0关闭autossh自带的监控端口改用SSH本身的ServerAliveInterval和ServerAliveCountMax机制来检测连接状态。这是新版autossh推荐的用法避免占用额外端口-N不执行远程命令只做端口转发。纯粹的隧道模式-R就是前面说的反向代理参数为了让连接更健壮我习惯在home-box的~/.ssh/config里给public-box单独写一段配置Host public-box HostName public-box-ip User user ServerAliveInterval 30 ServerAliveCountMax 3 ExitOnForwardFailure yesServerAliveInterval 30每30秒向服务器发一次心跳确认连接还活着ServerAliveCountMax 3连续3次心跳没响应才判定连接断开ExitOnForwardFailure yes如果端口转发绑定失败立即退出会话这能让autossh立刻感知到失败并重连这个配置文件是实测下来最稳的组合跑了大半年没掉过链子。3.3 注册成systemd服务实现开机自启autossh能保证连接断了自动重连但如果你家服务器重启了autossh进程本身也得跟着起来。所以最后一步是把它注册成systemd服务。在home-box上新建一个服务文件sudo vim /etc/systemd/system/autossh-tunnel.service写入以下内容[Unit] DescriptionAutoSSH tunnel to public box Afternetwork-online.target Wantsnetwork-online.target [Service] Userubuntu ExecStart/usr/bin/autossh -M 0 -N -R 8080:localhost:3000 userpublic-box-ip Restartalways RestartSec30 EnvironmentAUTOSSH_GATETIME0 [Install] WantedBymulti-user.target有几个细节值得注意Restartalways只要进程退出就无条件重启配合RestartSec30防止疯狂重连AUTOSSH_GATETIME0这个环境变量很关键意思是让autossh在启动后立刻检查连接状态而不是等默认的30秒观察期。如果不等这个观察期系统刚启动网络还没就绪时autossh不会误判自己失败了就不重连Userubuntu用普通用户跑别用root。这样即使用户家目录下的~/.ssh/config配置路径也对得上然后启用并启动服务sudo systemctl daemon-reload sudo systemctl enable autossh-tunnel.service sudo systemctl start autossh-tunnel.service查看状态sudo systemctl status autossh-tunnel.service一切正常的话你会看到服务处于active (running)状态然后在public-box上测试一下curl http://localhost:8080如果返回了你家里Web服务的内容说明整条链路已经通了。4. 实测中的坑与排查链路4.1 连接成功了却访问不了排查思路还原这是我帮朋友调的时候遇到的真问题。autossh显示连接建立了公网服务器上也确实能curl localhost:8080但从他电脑上访问公网IP的8080端口就是不通。当时我排查的顺序是这样的第一步先确认GatewayPorts。查了公网服务器的sshd_config发现这行被注释着默认no端口只绑定在127.0.0.1上。改成yes重启sshd之后问题依然存在。第二步检查云服务器安全组。朋友的这台公网服务器是云服务器我上控制台一看8080端口果然没在安全组规则里。放行端口之后公网IP:8080就能访问了。这里想强调一个排查思路当隧道看起来正常但外部访问不了时按本机到本机 → 本机到外部 → 云平台到外部的顺序逐层排查。先看隧道两端本身通不通再看防火墙ufw/iptables最后看云平台安全组。很多人一上来就怀疑隧道配置其实大概率是某个防火墙层面把端口拦了。检查public-box的监听状态可以用ss -tlnp | grep 8080如果输出里看到的是0.0.0.0:8080说明监听地址没问题如果是127.0.0.1:8080说明GatewayPorts没生效或者sshd没重载配置。4.2 一台公网服务器多台内网机器怎么映射实际工作中你极有可能遇到这种情况家里有NAS、有一台Ubuntu服务器、还有一台树莓派三台机器都想通过同一台公网服务器远程访问。这时候就需要合理分配公网服务器上的端口。比如这样规划18080 → 内网NAS的管理端口500018022 → 内网Ubuntu服务器的SSH端口2218090 → 树莓派上的某个应用端口8080于是autossh服务里配置就成了autossh -M 0 -N \ -R 18080:192.168.1.2:5000 \ -R 18022:192.168.1.3:22 \ -R 18090:192.168.1.4:8080 \ userpublic-box-ip注意这里我直接用的内网IP而不是localhost因为三条隧道分别指向三台不同的机器localhost只能代表当前执行命令的这台机器。你也可以在每台内网机器上各自启动自己的autossh隧道各开各的端口互不干扰。我个人的习惯是如果机器少就在一台上门多配几条隧道统一管理方便如果内网机器很多就每台各自跑一条隧道这样某台机器重启不会影响其他条目。4.3 多跳场景先跳堡垒机再转发还有一种常见场景你不在内网但也不是直接用公网服务器中转而是要通过公司的跳板机堡垒机才能访问内部网络。比如你人在外面要先ssh登录到公司的跳板机再通过跳板机访问内部的测试服务器。SSH反向代理在这种场景下照样管用。假设你从家里那台Ubuntu机器在外部网络发起隧道让跳板机上的某个端口指向公司内网的一台测试服务器命令是ssh -R 2222:10.0.0.5:22 userjump-box这样你在跳板机上ssh -p 2222 userlocalhost就相当于直接连到了10.0.0.5这台内网测试服务器。这在调试内网环境的时候非常方便——不需要在跳板机上装任何代理工具就用你手里的SSH客户端就能做到。如果内网测试服务器的SSH端口不是22就改成对应端口即可。这种多跳但不装额外软件的特性是SSH反向代理被很多运维老手当作随身百宝箱的原因。4.4 配合vscode远程开发的正确姿势vscode的Remote-SSH插件应该是现在远程开发的主力工具了它和SSH反向代理搭配起来效果很好。很多人的需求是人在外面笔记本上的vscode想连到家里Ubuntu服务器上写代码。思路是这样家里Ubuntu服务器的22端口通过反向代理暴露在公网服务器的某个端口上。假设你映射的是-R 2222:localhost:22那么在vscode里新建一个SSH连接填userpublic-box-ip -p 2222vscode就会先连上公网服务器然后通过隧道进入家里的Ubuntu服务器。不要忘了在vscode的SSH配置里~/.ssh/config加上Host home-server HostName public-box-ip Port 2222 User ubuntu ServerAliveInterval 30这样在vscode里直接连home-server就能一键进入不用每次手打端口。实测下来写代码的延迟体感和内网直连差别不大毕竟SSH隧道本身走的是长连接不像每次都重建连接。5. 安全加固与扩展玩法5.1 内网机器上的SSH安全加固远程通道打开之后安全问题不能忽视。首当其冲的是你的内网机器暴露出来的SSH端口。虽然你只是把它映射到了公网服务器的某个高位端口上但扫描器不分端口高低一样会来探测。几个必须做的加固措施第一禁用密码登录只允许密钥登录。改/etc/ssh/sshd_configPasswordAuthentication no ChallengeResponseAuthentication no UsePAM no然后sudo systemctl restart sshd。这一步把暴力破解的窗口直接关掉了。第二如果你在公网服务器上对所有人开放了GatewayPorts yes那就要小心别人也来连你映射出来的端口。可以在public-box上用ufw做端口白名单sudo ufw allow from your-ip to any port 8080 sudo ufw deny 8080先放行你常用的IP再拒绝其他来源这样隧道虽然打开了但只有你的IP能访问。第三给反向代理映射端口换一个不常见的端口号。别用8080、22这种一眼就知道的端口换成52000之类的随机高位端口能少很多扫描噪音。5.2 多个内网Web服务用nginx统一转发有些时候你内网跑了好几个Web服务比如一个Grafana、一个Jenkins、一个文件管理服务如果每个都在公网服务器上映射一个端口端口多不说还得记哪个端口对应哪个服务。一个更优雅的做法是公网服务器上装nginx所有HTTP流量都走80或443端口nginx根据域名或路径把请求转发到不同的反向代理隧道端口上。比如server { listen 80; server_name grafana.example.com; location / { proxy_pass http://127.0.0.1:18080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }你内网服务器上只需要把Grafana的端口映射到公网服务器的18080然后nginx再监听80端口按域名分流。这样外部用户只访问一个端口nginx负责做路由分发。这就是标题里提到的SSH反向代理和nginx反向代理的组合打法——SSH解决内网穿透nginx解决流量分发各管一段。这条路线的最大好处是干净、可扩展。以后内网多了一个服务只需要在autossh命令里加一条-R映射再在nginx配置里加一个location块就行公网只暴露80端口安全风险也相对小。5.3 日志监控与快速排障最后说说运维层面的东西。通道搭好之后怎么知道它活得好不好第一SSH的日志。在public-box上看/var/log/auth.log能查到所有SSH连接记录sudo tail -f /var/log/auth.log | grep sshd第二TCP连接状态。在public-box上数一下来自你家home-box的连接条数ss -tnp | grep :22第三我建议写一个简单的监控脚本每5分钟检查一次隧道是否还通。比如用cron定时任务curl一下映射出来的端口如果连续几次失败就发告警。不想折腾的话也可以直接监控autossh-tunnel.service的systemd状态服务意外退出时systemd会自动拉起配合Restartalways基本不需要人工干预。隧道断了但autossh没重连的情况我遇到过一回原因是home-box的IP换了DHCP重新分配然后~/.ssh/config里指定的HostName还是旧IP。换到内网静态IP或者用ddns动态域名解析就能解决。这算是一个容易被忽略的小坑。6. 写在最后的一些体会用了这么多年SSH反向代理我最大的感受是它不是那种炫技的方案但它是那种关键时刻最靠得住的方案。不依赖第三方软件、不依赖厂商平台、不额外增加运维负担只要你会用SSH就一定能用明白它。如果你刚开始折腾我的建议是先在一个临时端口上打通一条通道感受一下内网机器通过公网服务器被外部访问的这个过程然后再逐步加上autossh自动重连、systemd开机自启、nginx统一入口这些进阶内容。每加一层你都会对SSH这个老朋友的认识又深一层。最后分享一个实用的小习惯我常把命令里的-N换成-o ServerAliveInterval30 -o ServerAliveCountMax3 -o ExitOnForwardFailureyes也就是不依赖配置文件直接把参数写进命令里。这样在临时环境比如排查问题、帮别人调网络里不需要改对方的配置文件也能确保隧道稳定。当然如果是长期跑了还是建议写进~/.ssh/config毕竟一目了然方便后人维护。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑