目录- 本地端口转发- 使用堡垒主机进行本地端口转发- 远程端口转发- 向家庭或私有网络进行远程端口转发- 动态本地端口转发- 动态远程端口转发- 总结- 实践- 参考资源本文以清晰直观的方式解释了 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。- privateVPC 172.16.0.0/24 中的内部服务如数据库、OpenSearch 集群无法从公网访问。你可以从 local 通过主机名或 IP 地址 ssh 到 remote因为 local 的主机密钥已在 remote 机器上被信任ssh remotessh 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:8080ssh -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 CLIcurl -sf https://labs.iximiuz.com/cli/install.sh | sh这将下载并安装最新版本的 labctl CLI。每个工作站只需执行一次。授权 labctllabctl 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)
SSH 隧道实用指南:本地与远程端口转发全解析,助你成隧道高手!
发布时间:2026/6/25 21:56:07
目录- 本地端口转发- 使用堡垒主机进行本地端口转发- 远程端口转发- 向家庭或私有网络进行远程端口转发- 动态本地端口转发- 动态远程端口转发- 总结- 实践- 参考资源本文以清晰直观的方式解释了 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。- privateVPC 172.16.0.0/24 中的内部服务如数据库、OpenSearch 集群无法从公网访问。你可以从 local 通过主机名或 IP 地址 ssh 到 remote因为 local 的主机密钥已在 remote 机器上被信任ssh remotessh 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:8080ssh -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 CLIcurl -sf https://labs.iximiuz.com/cli/install.sh | sh这将下载并安装最新版本的 labctl CLI。每个工作站只需执行一次。授权 labctllabctl 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)