当前位置: 首页 > news >正文

SSH 隧道实用指南:本地与远程端口转发全解析,助你成隧道高手!

目录

- 本地端口转发
- 使用堡垒主机进行本地端口转发
- 远程端口转发
- 向家庭或私有网络进行远程端口转发
- 动态本地端口转发
- 动态远程端口转发
- 总结
- 实践
- 参考资源

本文以清晰直观的方式解释了 SSH 端口转发,介绍了如何使用本地和远程端口转发,以及可能需要调整的 sshd 设置,还分享了记忆正确标志的方法。

SSH 是古老技术至今仍广泛应用的典型例子(可参考 [这篇文章](https://iximiuz.com/en/posts/linux-pty-what-powers-docker-attach-functionality/))。从长远来看,掌握一些 SSH 技巧或许比精通一堆下个季度就可能过时的云原生工具或 AI 代理框架更有用。

我特别喜欢 SSH 技术中的 SSH 隧道。只需使用标准工具,通常一个命令就能实现以下功能:
- 通过面向公网的 EC2 实例访问内部 VPC 端点。
- 在本地浏览器中打开远程开发虚拟机的 `localhost` 端口。
- 将家庭或私有网络中的任何本地服务器暴露到外部网络。
- [将浏览器的调试端口通过隧道传输到远程沙盒编码代理](/docs/playground-recipes/coding-agent-with-browser-access)。

还有更多强大功能😍

尽管我每天都使用 SSH 隧道,但每次都得花点时间回忆正确的命令。到底该用本地隧道还是远程隧道?标志是什么?是 `本地端口:远程端口` 还是反过来?于是,我决定彻底搞清楚这个问题,最终整理出了一系列实验和一份可视化速查表。

[完整版 8.8MB](/content/files/tutorials/ssh-tunnels/__static__/ssh-tunnels-cheat-sheet-orig.png)

本教程中的实验在附带的实验环境中进行,该环境有四个主机,连接到三个网络:
- `internal`:家庭网络 `192.168.0.0/24` 中的设备(如家庭实验室设备、NAS、打印机),无法从公网访问。
- `local`:你的工作站,同时连接到家庭网络 `192.168.0.0/24` 和公网 `203.0.113.0/24`。
- `remote`:公网 `203.0.113.0/24` 上的面向公网的堡垒主机/网关,还连接到私有 VPC `172.16.0.0/24`。
- `private`:VPC `172.16.0.0/24` 中的内部服务(如数据库、OpenSearch 集群),无法从公网访问。

你可以从 `local` 通过主机名或 IP 地址 `ssh` 到 `remote`,因为 `local` 的主机密钥已在 `remote` 机器上被信任:
ssh remote
ssh 203.0.113.30

复制到剪贴板

本地端口转发

先从我最常用的本地端口转发说起。很多时候,远程机器的 `localhost` 或私有接口上运行着某个服务,我只能通过其公网 IP 进行 SSH 连接,但又急需从本地机器访问这个端口。以下是一些常见的例子:
- 使用你喜欢的 UI 工具从笔记本电脑访问私有远程数据库(如 MySQL、Postgres、Redis 等)。
- 使用浏览器访问仅暴露给私有网络的 Web 应用程序。
- 从笔记本电脑访问容器端口,而无需将其发布到服务器的公网接口。

以上所有用例都可以通过一个 `ssh` 命令解决:
ssh -L [本地地址:]本地端口:远程地址:远程端口 [用户@]sshd 地址
复制到剪贴板

`-L` 标志表示我们正在启动 _本地端口转发_。它的实际含义是:
- 在本地机器上,SSH 客户端将开始监听 `本地端口`(通常是 `localhost`,但具体取决于 [检查 `GatewayPorts` 设置](https://linux.die.net/man/5/sshd_config#GatewayPorts))。
- 任何发往该端口的流量都将被转发到 `远程地址:远程端口`,该地址从你 SSH 连接的远程机器可达。

下面是它的示意图:

专业提示:使用 `ssh -f -N -L` 让端口转发会话在后台运行。

实验 1:使用 SSH 隧道进行本地端口转发 👨‍🔬

这个实验重现了上述示意图的设置。`remote` 主机运行一个绑定到 `127.0.0.1:80` 的 Web 服务器,我们想从 `local` 工作站访问它。

由于该服务绑定到回环接口,无法通过网络访问。从本地主机尝试访问 `remote` 主机的公网地址:
curl 203.0.113.30:80 # remote.public
复制到剪贴板
curl: (7) Failed to connect to 203.0.113.30 port 80 after 0 ms: Could not connect to server
复制到剪贴板

但在远程主机内部,同样的服务运行正常:
curl localhost:80
复制到剪贴板
Hello from the remote host (localhost-only service).
复制到剪贴板

关键来了:回到本地主机,使用本地端口转发将远程的 `localhost:80` 绑定到本地的 `localhost:8080`:
ssh -f -N -L 8080:localhost:80 203.0.113.30
复制到剪贴板

现在你可以在工作站的本地端口访问该 Web 服务:
curl localhost:8080
复制到剪贴板
Hello from the remote host (localhost-only service).
复制到剪贴板

另一种稍微详细(但更明确和灵活)的实现方式:
ssh -f -N -L localhost:8080:localhost:80 203.0.113.30
# 本地 远程 通过

复制到剪贴板

使用堡垒主机进行本地端口转发

乍一看可能不太明显,但 `ssh -L` 命令允许将本地端口转发到 _任何机器_ 上的远程端口,而不仅仅是 SSH 服务器本身。注意 `远程地址` 和 `sshd 地址` 的值可能相同也可能不同:
ssh -L [本地地址:]本地端口:远程地址:远程端口 [用户@]sshd 地址
复制到剪贴板

用于访问私有目标的远程 SSH 服务器通常被称为 [_堡垒主机或跳板主机_](https://en.wikipedia.org/wiki/Bastion_host)。我是这样在脑海中想象这个场景的:

我经常使用上述技巧来调用从 _堡垒主机_ 可访问但从我的笔记本电脑无法访问的端点(例如,使用具有私有和公网接口的 EC2 实例连接到 OpenSearch 集群或任何其他完全部署在 VPC 内的服务)。

实验 2:使用堡垒主机进行本地端口转发 👨‍🔬

这个实验重现了上述示意图的设置。远程目标服务运行在模拟 VPC 网络中的 `private` 主机上(`172.16.0.40:80`),之前的 `remote` 主机充当我们面向公网的堡垒(跳板)主机,可以访问该服务。

`local` 工作站无法直接访问 VPC 网络,因此无法直接与 `private` 主机通信。从本地主机尝试访问:
curl --connect-timeout 3 172.16.0.40:80 # private.vpc
复制到剪贴板
curl: (28) Connection timed out after 3002 milliseconds
复制到剪贴板

另一方面,`remote` 堡垒主机连接到 VPC 网络,可以访问 `private` 主机。因此,我们通过堡垒主机将本地端口直接转发到私有服务。从本地主机执行:
ssh -f -N -L 8081:172.16.0.40:80 203.0.113.30
复制到剪贴板

在本地主机上验证是否成功:
curl localhost:8081
复制到剪贴板
Hello from the private VPC host (172.16.0.40).
复制到剪贴板

注意,转发目标 (`172.16.0.40`) 和 SSH 服务器 (`203.0.113.30`) 是不同的机器。堡垒主机接受连接,并代表我们向私有主机发起第二次连接。

另一种稍微详细(但更明确和灵活)的实现方式:
ssh -f -N -L localhost:8081:172.16.0.40:80 203.0.113.30
# 本地 远程 通过

复制到剪贴板

远程端口转发

另一个常见(但逻辑相反)的场景是,你想暂时将本地服务暴露到外部网络。当然,这需要一个 _面向公网的入口网关服务器_。好消息是,任何运行 SSH 守护进程的公网服务器都可以用作这样的网关:
ssh -R [远程地址:]远程端口:本地地址:本地端口 [用户@]网关地址
复制到剪贴板

上述命令看起来并不比 `ssh -L` 复杂,但有个陷阱...

默认情况下,上述 SSH 隧道只允许使用网关的 `localhost` 作为远程地址。换句话说,你的本地端口只能从网关服务器内部访问,这可能不是你真正想要的。例如,我通常希望使用网关的公网地址作为远程地址,将本地服务暴露到公网。为此,需要在 SSH 服务器上配置 [`GatewayPorts yes`](https://linux.die.net/man/5/sshd_config#GatewayPorts) 设置。

远程端口转发的应用场景如下:
- 将笔记本电脑上的开发服务暴露到公网,以便快速演示。
- 将家庭实验室暴露到公网(用于各种目的)。
- [将本地浏览器的调试端口通过隧道传输到远程和/或沙盒编码代理](/docs/playground-recipes/coding-agent-with-browser-access)。

远程端口转发的可视化示意图如下:

专业提示:使用 `ssh -f -N -R` 让端口转发会话在后台运行。

实验 3:使用 SSH 隧道进行远程端口转发 👨‍🔬

这个实验重现了上述示意图的设置。`local` 工作站运行一个绑定到 `127.0.0.1:80` 的 Web 服务器,我们想通过面向公网的 `remote` 网关将其暴露到外部网络。

由于该服务绑定到回环接口,目前只有 `local` 机器本身可以访问它。从远程机器尝试访问:
curl --connect-timeout 3 203.0.113.20:80 # local.public
复制到剪贴板
curl: (7) Failed to connect to 203.0.113.20 port 80 after 0 ms: Could not connect to server
复制到剪贴板

我们想通过 `remote` 网关将其暴露出去,并从 `private` 主机访问。`remote` 网关的 `sshd_config` 中已经配置了 `GatewayPorts yes`,因此我们可以让它监听所有接口 (`0.0.0.0`),并将流量转发回我们。不过,`local` 机器必须首先建立隧道

从本地主机启动远程端口转发:
ssh -f -N -R 0.0.0.0:8080:localhost:80 203.0.113.30
# 远程 本地 通过

复制到剪贴板

现在,本地 Web 服务已发布到网关的接口上。让我们从第三台机器(`private` 主机,它可以通过 VPC 访问 `remote` 网关)来确认:
curl 172.16.0.30:8080 # remote.vpc
复制到剪贴板
Hello from your local workstation (localhost-only service).
复制到剪贴板

向家庭或私有网络进行远程端口转发

与本地端口转发类似,远程端口转发也有自己的 _堡垒或跳板主机_ 模式。但这次,运行 SSH 客户端的机器(如你的开发笔记本电脑)充当跳板主机。具体来说,它允许通过充当入口网关的远程 SSH 服务器,将从你的笔记本电脑可访问的家庭(或私有)网络的端口暴露到外部网络:
ssh -R [远程地址:]远程端口:本地地址:本地端口 [用户@]网关地址
复制到剪贴板

这看起来与简单的远程 SSH 隧道几乎相同,但 `本地地址:本地端口` 变成了家庭网络中设备的地址。下面是它的示意图:

我通常将笔记本电脑用作瘦客户端,实际开发在远程服务器上进行。有时,这样的远程服务器可能位于我的家庭网络中,并且没有或只有受限的互联网访问(以增强隔离性)。这时,我可能会依靠远程端口转发,通过我的笔记本电脑(它可以访问内部开发服务器和远程 SSH 服务器(入口网关))作为跳板主机,将家庭服务器上的服务暴露到公网。

实验 4:从家庭/私有网络进行远程端口转发 👨‍🔬

这个实验重现了上述示意图的设置。我们想暴露的服务运行在隔离的家庭网络中的 `internal` 主机上(`192.168.0.10:80`)。我们的 `local` 工作站可以访问家庭网络,并且可以通过 SSH 访问面向公网的 `remote` 网关,因此它充当跳板主机。

`local` 主机可以通过家庭网络访问 `internal` 服务。从本地主机尝试访问:
curl 192.168.0.10:80 # internal.home
复制到剪贴板
Hello from the internal home-network host (192.168.0.10).
复制到剪贴板

但从外部网络,`internal` 设备是不可见的。从远程主机尝试访问:
curl --connect-timeout 3 192.168.0.10:80 # internal.home
复制到剪贴板
curl: (28) Connection timed out after 3001 milliseconds
复制到剪贴板

`remote` 主机无法访问家庭网络,因此请求超时。

现在,从本地主机启动从 `remote` 网关到 `internal` 设备的远程端口转发。转发目标 (`192.168.0.10`) 由 SSH 客户端解析,即从 `local` 主机的角度来看:
ssh -f -N -R 0.0.0.0:8081:192.168.0.10:80 203.0.113.30
# 远程 本地 通过

复制到剪贴板

最后,从 `private` 主机(它可以通过 VPC 访问网关)验证家庭网络服务是否可以通过网关访问:
curl 172.16.0.30:8081 # remote.vpc
复制到剪贴板
Hello from the internal home-network host (192.168.0.10).
复制到剪贴板

动态本地端口转发

这种转发模式对客户端来说不太直观,但比常规的本地端口转发灵活得多。与 `ssh -L` 将本地端口连接到单个远程目标不同,动态(本地)端口转发将 SSH 客户端变成一个本地 [SOCKS 代理](https://en.wikipedia.org/wiki/SOCKS)。任何支持 SOCKS 协议的应用程序都可以通过它发送流量,每个连接可以选择实际的目标主机和端口,这些请求将被发送到 SSH 服务器,由服务器解析目标并建立连接:
ssh -D [本地地址:]本地端口 [用户@]sshd 地址
复制到剪贴板

使用 `-D` 标志时,本地机器上的 SSH 客户端会启动一个 SOCKS 代理,监听 `本地端口`(默认是 `localhost`)。通过代理的每个连接都会被转发到 SOCKS 客户端请求的任何地址,该地址从 `sshd 地址` 机器可达。

换句话说,它类似于 `ssh -L`,但你不必提前指定单个 `远程地址:远程端口`,因为 SOCKS 协议允许在每个连接开始时指定目标(通过在有效负载之前发送的几个额外字节)。一个(本地)代理端口可以让你访问从(远程)SSH 服务器可达的 _每个_ 主机和端口。

动态端口转发的应用场景如下:
- 通过堡垒主机调用私有网络中的 API,无需为每个服务单独设置隧道。
- 通过单个跳板主机浏览远程网络中的内部 Web 应用程序。
- 通过一个 EC2 实例从笔记本电脑访问一组 VPC 端点。

专业提示:使用 `ssh -f -N -D` 让 SOCKS 代理在后台运行。

实验 5:使用 SSH 隧道进行动态端口转发 👨‍🔬

这是实验 2 中的堡垒主机场景,不过这次我们不会将隧道固定到单个目标。

首先,确保我们无法从本地机器访问 `private` 目标:
curl --connect-timeout 3 172.16.0.40:80 # private.vpc
复制到剪贴板
curl: (28) Connection timed out after 3002 milliseconds
复制到剪贴板

现在,在本地主机上通过 `remote` 主机启动一个 SOCKS 代理:
ssh -f -N -D 1080 203.0.113.30 # remote.public
复制到剪贴板

如果将 `curl` 指向代理以访问 `private` VPC 服务,请求将成功:
curl --socks5-hostname localhost:1080 172.16.0.40:80
# 通过 private.vpc

复制到剪贴板
Hello from the private VPC host (172.16.0.40).
复制到剪贴板

请注意,与 `ssh -L` 不同,客户端(这里是 `curl`)必须支持 SOCKS 协议(见 `--socks5-hostname` 标志)。

同一个 SOCKS 代理可以访问 `remote` 机器可达的 _任何_ 主机,包括第二个 VPC 主机。尝试访问 `private-2` 机器:
curl --socks5-hostname localhost:1080 172.16.0.50:80
# 通过 private-2.vpc

复制到剪贴板
Hello from the second private VPC host (172.16.0.50).
复制到剪贴板

使用 `ssh -L` 访问两个私有主机需要设置两个单独的隧道(每个 `远程地址:远程端口` 一个),而一个 `ssh -D` 代理可以覆盖堡垒主机后面的整个网络。

动态远程端口转发

就像 `ssh -L` 有动态版本 `ssh -D` 一样,`ssh -R` 命令也有自己的动态模式。如果你去掉 `-R` 中的固定目标,只传递一个端口,OpenSSH 会将SSH 服务器本身变成一个 SOCKS 代理。这与 `-D` 正好相反:这次代理位于网关,通过它的每个连接都会通过隧道返回 `ssh` 客户端,并从 _客户端_ 的角度解析:
ssh -R [绑定地址:]端口 [用户@]网关地址
复制到剪贴板

`-R` 标志没有目标意味着
- 在远程网关,SSH 服务器启动一个 SOCKS 代理,监听 `端口`(默认是网关的 `localhost`,如果设置了 `GatewayPorts yes` 则监听所有接口)。
- 通过代理的每个连接都会通过隧道返回 `ssh` 客户端,并转发到 SOCKS 客户端请求的任何地址,该地址从客户端一侧可达。

这类似于常规的 `ssh -R`,但你不必提前选择单个 `本地地址:本地端口`。网关上的一个代理可以暴露从 `ssh` 客户端可达的 _每个_ 主机和端口,例如整个家庭网络。

远程动态转发要求客户端使用 OpenSSH 7.6 或更高版本。与常规的 `ssh -R` 一样,将代理绑定到网关的非回环地址需要在其 `sshd_config` 中设置 `GatewayPorts yes`。

专业提示:使用 `ssh -f -N -R` 让 SOCKS 代理在后台运行。

实验 6:使用 SSH 隧道进行远程动态端口转发 👨‍🔬

这是实验 4 中的家庭网络场景,我们想通过面向公网的 `remote` 网关暴露只有 `local` 可以访问的设备,不过这次一个代理可以覆盖所有设备。

首先,确保我们无法从 `private` 机器访问 `internal` 主机:
curl 192.168.0.10:80 # internal.home
复制到剪贴板
curl: (7) Failed to connect to 192.168.0.10 port 80 after 0 ms: Could not connect to server
复制到剪贴板

现在,从本地主机将 `remote` 网关变成一个 SOCKS 代理,并与它建立隧道:
ssh -f -N -R 0.0.0.0:1080 203.0.113.30 # remote.public
复制到剪贴板

为了再次检查连接性,从 `private` 主机使用网关的代理访问 `internal` 家庭设备:
curl --socks5-hostname 172.16.0.30:1080 192.168.0.10:80
# 通过 internal.home

复制到剪贴板
Hello from the internal home-network host (192.168.0.10).
复制到剪贴板

同一个代理可以访问 `ssh` 客户端 (`local`) 可达的任何设备,包括其自身的回环服务:
curl --socks5-hostname 172.16.0.30:1080 127.0.0.1:80
# 通过 local 的 localhost

复制到剪贴板
Hello from your local workstation (localhost-only service).
复制到剪贴板

总结

以下是快速回顾和一些助记方法,帮助你记住 SSH 隧道命令:
-本地端口转发(`ssh -L`):使远程服务在本地端口可用。
-远程端口转发(`ssh -R`):使本地服务在远程端口可用。
-动态本地端口转发(`ssh -D`):将本地 `ssh` 客户端变成一个 SOCKS 代理。
-动态远程端口转发(`ssh -R` 无目标):将 `sshd` 服务器变成一个 SOCKS 代理。
- 本地端口转发 (`ssh -L`) 意味着 `ssh` 客户端开始监听一个新端口。
- 远程端口转发 (`ssh -R`) 意味着 `sshd` 服务器开始监听一个额外的端口。
-本地可以指SSH 客户端机器或从它可访问的内部主机。
-远程可以指SSH 服务器机器 (sshd)或从它可访问的任何主机。
- 助记方法是 _ssh-Llocal:remote_ 和 _ssh-Rremote:local_,总是左边的部分打开一个新端口。

希望以上内容能帮助你成为 SSH 隧道的高手🧙

实践

通过解决以下实际挑战来巩固你的学习成果:

参考资源

- [SSH 隧道详解](https://goteleport.com/blog/ssh-tunneling-explained/),由 Teleport 的网络专家撰写。
- [SSH 隧道:示例、命令、服务器配置](https://www.ssh.com/academy/ssh/tunneling-example),来自 SSH Academy。

关于作者

# [Ivan Velichko](/a/ivan-velichko)

Ivan 是 iximiuz Labs 的创建者,也是一位资深的技术博主和教育者,主要关注服务器端技术和容器领域。

在网上找到这位作者
[](https://github.com/iximiuz "GitHub")[](https://www.linkedin.com/in/iximiuz "LinkedIn")[](https://twitter.com/iximiuz "Twitter (X)")[](https://iximiuz.com "网站")

撰写主题
容器、Linux、网络

经常涉及的内容
Docker、容器镜像、容器基础、容器运行时、容器注册表

如何在 iximiuz Labs 撰写教程

iximiuz Labs 没有提供欠佳的在线编辑体验,而是提供了一个名为 [labctl](https://github.com/iximiuz/labctl) 的辅助 CLI 工具,让你可以使用喜欢的文本编辑器(或功能齐全的 IDE)在本地机器上舒适地撰写内容。

安装 labctl CLI

curl -sf https://labs.iximiuz.com/cli/install.sh | sh
这将下载并安装最新版本的 labctl CLI。每个工作站只需执行一次。

授权 labctl

labctl auth login
这将打开一个浏览器窗口,要求你授权 labctl 访问你的账户。在新安装 labctl 后需要执行此操作,并且每当认证会话过期时都需要重复执行。

拉取教程内容

labctl content pull tutorial ssh-tunnels
这将在名为 `ssh-tunnels` 的目录中创建教程内容的本地副本。每个教程只需执行一次。

实时同步更改

labctl content push -fw tutorial ssh-tunnels
在另一个终端中运行此命令,在你使用喜欢的文本编辑器或 IDE 编辑教程时,将持续将更改上传到服务器。

你还可以使用 labctl 创建、列出和删除你的内容。了解更多可用命令:`labctl content --help`

[ 开始教程](/signup?return_to=%2Ftutorials%2Fssh-tunnels)

http://www.jsqmd.com/news/1077464/

相关文章:

  • AI伦理实战课:从数据采集协议到上线备案的工程化落地
  • 2026年单北斗GNSS变形监测产品推荐,引领精确监控新风尚
  • 2026年小程序卖货平台搭建哪家好?适合商家的商城系统推荐
  • 计算机毕业设计之基于Java的小区业主服务平台的设计与实现
  • 从零到生产级:Ubuntu桌面/WSL2/Server三种场景下IntelliJ IDEA静默安装脚本(bash + ansible + systemd unit全栈交付)
  • NL2SQL技术原理与实战指南
  • 多播组成员动态加入退出时如何实现毫秒级状态同步与故障隔离
  • NLP语义脉搏监测系统:用知识图谱解码技术演进
  • 深入解析musl libc的TLS初始化机制:从__init_tp到线程局部存储
  • 销售分析怎么做?优秀的销售分析分析都离不开细分思维
  • 2026上半年AI视频模型演进:从Seedance 2.0到Hedra Avatar的工程实践
  • 呼市全屋定制工厂,2026年6月亲测推荐
  • 2026年GEO优化系统源码二次开发,如何抢占流量新风口?
  • 限时解密:JetBrains官网未公开的离线安装包获取通道,以及如何绕过Activation Server验证(仅限教育邮箱与开源项目认证用户)
  • MSC8112系统总线地址空间解析:从物理地址到外设控制实战
  • PPTist:免费网页版PPT制作工具,3分钟快速创建专业演示文稿
  • 英雄联盟智能助手Seraphine:终极战绩查询与自动BP工具完全指南
  • 房地产ERP系统HTTP头注入漏洞实战:X-Forwarded-For引发的SQL注入攻防
  • C++队列(练习题)
  • 【毕业设计】基于 Django 的医疗数据可视化辅助诊疗系统的设计与实现 基于 Django 的社区智能医疗服务辅助系统(源码+文档+远程调试,全bao定制等)
  • 极低温测量的“狙击手”:DABT-PT509高精度PT100采集卡与工业物联网接入实战
  • 网安小白入门科普!全方位讲解 CTF 网络安全赛事,手把手拆解赛制题型,零基础快速看懂 CTF 比赛
  • LSO TSO对性能的影响
  • 越华环保智能危废暂存库:三层数字化架构的落地实践
  • 链动 2+1 模式合规吗?小程序商城源码如何实现走人留人机制、规避分销风险?
  • 计算机毕业设计之基于小程序的高校自习室管理系统
  • RxSwift:iOS 开发者的响应式编程工具箱
  • SSRF漏洞攻防全解析:从原理到RCE的完整攻击链
  • GDRE Tools:专业级Godot逆向工程工具深度解析
  • 驻马店汽车贴膜排名前十揭秘:谁家贴车衣最靠谱?