SSH 隧道实用指南:本地和远程端口转发

Hacker News Top 工具

摘要

一份实用教程,通过图表和实验讲解 SSH 本地和远程端口转发,用于安全访问内部网络服务。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/24 13:52

# SSH 隧道实用指南:本地与远程端口转发 | iximiuz Labs 来源:https://labs.iximiuz.com/tutorials/ssh-tunnels SSH 又是一个古老但至今仍广泛使用的技术范例(https://iximiuz.com/en/posts/linux-pty-what-powers-docker-attach-functionality/)。长远来看,掌握几个 SSH 技巧可能比精通十多个即将在下季度被淘汰的云原生工具或 AI 代理框架更有价值。 这项技术中我最喜欢的部分之一是 SSH 隧道。只需使用标准工具,通常一条命令就能实现以下功能: - 通过公网 EC2 实例访问内部 VPC 端点。 - 在本地浏览器中打开远程开发虚拟机的 `localhost` 端口。 - 将家庭/私有网络中的任何本地服务器暴露给外部世界。 - 将浏览器的调试端口隧道到一个远程沙盒化的编码代理(https://labs.iximiuz.com/docs/playground-recipes/coding-agent-with-browser-access)。 以及更多😍 但尽管我每天都使用 SSH 隧道,每次回忆正确的命令都需要花点时间。应该是本地隧道还是远程隧道?参数是什么?是 `本地端口:远程端口` 还是反过来?所以,我决定彻底搞明白它,这最终形成了一系列实验和一个可视化备忘录。 SSH 端口转发备忘录。 本教程中的实验在一个附加的游乐场中运行,该游乐场包含四个主机,分布在三个网络中: - `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` - 一个仅内部的服务(数据库、OpenSearch 集群),位于**VPC** `172.16.0.0/24` 中。无法从**公共**网络访问。 你可以通过主机名或 IP 地址从 `local` SSH 到 `remote` —— `local` 主机的密钥已在 `remote` 机器上受信任: ``` ssh remote ssh 203.0.113.30 ``` ## 本地端口转发 从我最常用的一种开始。很多时候,远程机器的 `localhost` 或私有接口上可能有服务在监听,而我只能通过其公网 IP SSH 到该机器。但我迫切需要从本地机器访问这个端口。一些典型例子: - 使用你喜欢的 UI 工具从笔记本电脑访问私有远程数据库(MySQL、Postgres、Redis 等)。 - 使用浏览器访问仅在私有网络上暴露的 Web 应用程序。 - 从笔记本电脑访问容器的端口,而无需将端口发布到服务器的公网接口上。 以上所有用例都可以通过一条 `ssh` 命令解决: ``` ssh -L [local_addr:]local_port:remote_addr:remote_port [user@]sshd_addr ``` `-L` 标志表示我们正在启动**本地端口转发**。它实际意味着: - 在本地机器上,SSH 客户端将开始在 `local_port` 上监听(可能在 `localhost` 上,但**取决于情况**——请查看 `GatewayPorts` 设置(https://linux.die.net/man/5/sshd_config#GatewayPorts))。 - 发送到此端口的任何流量将被转发到 `remote_addr:remote_port`,从你 SSH 到的远程机器访问。 在示意图上如下所示: SSH 隧道可视化 - 本地端口转发。 **专业提示:** 使用 `ssh -f -N -L` 在后台运行端口转发会话。 实验 1:使用 SSH 隧道进行本地端口转发 👨🔬 本实验复现了上图所示设置。`remote` 主机运行一个绑定到 `127.0.0.1:80` 的 Web 服务器,我们想从 `local` 工作站访问它。 由于服务绑定到环回接口,无法通过网络访问。从 localhost 尝试访问 `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 ``` 但从 remote 主机内部,同样的服务可以正常运行: ``` Hello from the remote host (localhost-only service). ``` **关键在于:** 回到 localhost,使用本地端口转发将 remote 的 `localhost:80` 绑定到本地的 `localhost:8080`: ``` ssh -f -N -L 8080:localhost:80 203.0.113.30 ``` 现在你可以在工作站的本地端口上访问该 Web 服务: ``` Hello from the remote host (localhost-only service). ``` --- 实现相同目标的更详细(但更明确灵活)方式: ``` ssh -f -N -L localhost:8080:localhost:80 203.0.113.30 # 本地 远程 通过 ``` ## 使用堡垒主机的本地端口转发 一开始可能不明显,但 `ssh -L` 命令允许将本地端口转发到**任何机器**上的远程端口,而不仅仅是 SSH 服务器本身。请注意 `remote_addr` 和 `sshd_addr` 可能相同也可能不同: ``` ssh -L [local_addr:]local_port:remote_addr:remote_port [user@]sshd_addr ``` 用于访问私有目标的远程 SSH 服务器通常被称为**堡垒或跳板主机**(https://en.wikipedia.org/wiki/Bastion_host)。我在脑海中这样想象这个场景: SSH 隧道可视化 - 使用堡垒主机的本地端口转发。 我经常使用上述技巧来调用从**堡垒主机**可访问但不能从我的笔记本电脑访问的端点(例如,使用具有私有和公网接口的 EC2 实例连接到完全部署在 VPC 内的 OpenSearch 集群或其他服务)。 实验 2:使用堡垒主机的本地端口转发 👨🔬 本实验复现了上图所示设置。远程目标服务运行在 `private` 主机上,位于一个简易 VPC 网络内(`172.16.0.40:80`),而之前的 `remote` 主机充当我们的面向公网的堡垒(跳板主机),可以访问该服务。 `local` 工作站没有通往 VPC 的路由,因此无法直接与 `private` 主机通信。从 localhost: ``` curl --connect-timeout 3 172.16.0.40:80 # private.vpc ``` ``` curl: (28) Connection timed out after 3002 milliseconds ``` 另一方面,`remote` 堡垒连接到 VPC 并可以访问 `private` 主机。因此,我们通过堡垒将本地端口直接转发到私有服务。从 localhost: ``` ssh -f -N -L 8081:172.16.0.40:80 203.0.113.30 ``` 检查是否有效 - 仍在 localhost: ``` 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 [remote_addr:]remote_port:local_addr:local_port [user@]gateway_addr ``` 上面的命令看起来并不比 `ssh -L` 复杂。但有一个陷阱…… **默认情况下,上述 SSH 隧道只允许使用网关的 localhost 作为远程地址。** 换句话说,你的本地端口只能在网关服务器内部访问,这很可能不是你真正需要的。例如,我通常希望使用网关的公网地址作为远程地址,将我的本地服务暴露给公共互联网。为此,需要在 SSH 服务器上配置 `GatewayPorts yes`(https://linux.die.net/man/5/sshd_config#GatewayPorts)设置。 远程端口转发的用途: - 将笔记本电脑上的开发服务暴露给公共互联网,以便快速演示。 - 将家庭实验室暴露给公共互联网(任意目的)。 - 将本地浏览器的调试端口隧道到远程和/或沙盒化的编码代理(https://labs.iximiuz.com/docs/playground-recipes/coding-agent-with-browser-access)。 远程端口转发可视化如下: SSH 隧道可视化 - 远程端口转发。 **专业提示:** 使用 `ssh -f -N -R` 在后台运行端口转发会话。 实验 3:使用 SSH 隧道进行远程端口转发 👨🔬 本实验复现了上图所示设置。`local` 工作站运行一个绑定到 `127.0.0.1:80` 的 Web 服务器,我们想通过面向公网的 `remote` 网关将其暴露给外部。 服务绑定到环回接口,因此目前除了 `local` 机器本身之外,没有人可以访问它。尝试从 remote 机器访问它: ``` 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` 机器必须首先建立隧道。** 从 localhost 启动远程端口转发: ``` 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 [remote_addr:]remote_port:local_addr:local_port [user@]gateway_addr ``` 看起来与简单的远程 SSH 隧道几乎相同,但 `local_addr:local_port` 对变成了家庭网络中设备的地址。在示意图中如下所示: SSH 隧道可视化 - 远程端口转发到家庭网络。 我通常将笔记本电脑用作瘦客户端,实际开发在远程服务器上进行。有时,这样的远程服务器可以位于我的家庭网络中,并且没有或限制互联网访问(为了额外隔离)。这时我可能依赖远程端口转发,将从家庭服务器上的服务暴露给公共互联网,使用我的笔记本电脑(既可以访问内部开发服务器,也可以访问远程 SSH 服务器(入口网关))作为跳板主机。 实验 4:从家庭/私有网络的远程端口转发 👨🔬 本实验复现了上图所示设置。我们想要暴露的服务运行在 `internal` 主机上,位于一个隔离的家庭网络内(`192.168.0.10:80`)。我们的 `local` 工作站可以访问家庭网络,并且还可以通过 SSH 访问面向公网的 `remote` 网关,因此它扮演跳板主机的角色。 `local` 主机可以通过家庭网络访问 `internal` 服务。从 localhost: ``` curl 192.168.0.10:80 # internal.home ``` ``` Hello from the internal home-network host (192.168.0.10). ``` 然而,从外部看,`internal` 设备是不可见的。尝试从 remote 主机访问它: ``` curl --connect-timeout 3 192.168.0.10:80 # internal.home ``` ``` curl: (28) Connection timed out after 3001 milliseconds ``` `remote` 主机没有通往家庭网络的路由,因此请求超时。 现在,从 localhost,**启动从 `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 [local_addr:]local_port [user@]sshd_addr ``` 当使用 `-D` 标志时,本地机器上的 SSH 客户端启动一个 SOCKS 代理,监听在 `local_port`(默认在 `localhost` 上)。通过代理建立的每个连接被转发到 SOCKS 客户端请求的任何地址,从 `sshd_addr` 机器访问。 换句话说,它类似于 `ssh -L`,但你不必事先指定单个 `remote_addr:remote_port`,因为 SOCKS 协议允许在每个连接开始时(通过有效载荷之前发送的额外几个字节)指定目标。一个(本地)代理端口就能让你访问(远程)SSH 服务器可到达的**所有**主机和端口。 SSH 隧道可视化 - 动态本地端口转发(SOCKS 代理)。 动态端口转发的用途: - 通过堡垒调用私有网络中的 API,而无需为每个服务建立单独的隧道。 - 通过单个跳板主机浏览远程网络中的内部 Web 应用程序。 - 通过一个 EC2 实例从笔记本电脑访问 VPC 中的一系列端点。 **专业提示:** 使用 `ssh -f -N -D` 在后台运行 SOCKS 代理。 实验 5:使用 SSH 隧道进行动态端口转发 👨🔬 这又是实验 2 中的堡垒场景,只不过这次我们不将隧道固定到单个目标。 首先,确保从 local 机器无法访问 `private` 目标: ``` curl --connect-timeout 3 172.16.0.40:80 # private.vpc ``` ``` curl: (28) Connection timed out after 3002 milliseconds ``` 现在,在 localhost 上,通过 `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` 访问两个私有主机意味着需要两个单独的隧道(每个 `remote_addr:remote_port` 一个)。而单个 `ssh -D` 代理覆盖了所有。

相似文章

@PierceZhang34: SSH隧道实用指南 尽管每天都使用 SSH 隧道,但总是要花点时间才能想起正确的命令 应该是本地隧道还是远程隧道?需要哪些参数? 端口号是本地端口:远程端口,还是反过来? 这个作者通过一系列实验,制作了一份可视化速查表 传送门 http:…

X AI KOLs Timeline

This tutorial provides a visual cheat sheet and practical labs for SSH local and remote port forwarding, covering common use cases like accessing private databases and exposing local servers.

imthenachoman/如何保障 Linux 服务器安全

GitHub Trending (daily)

这是一份全面的开源指南与工具集,旨在保障 Linux 服务器的安全,涵盖使用 Ansible、Fail2Ban 和 Lynis 等工具进行 SSH 加固、防火墙配置以及入侵检测。

mikeroyal/Self-Hosting-Guide

GitHub Trending (daily)

全面指南,介绍如何在本地自托管软件应用程序,涵盖云服务、LLM、WireGuard、自动化、Home Assistant 和网络。