Skip to content

通过 SSH 端口转发访问开发机内 HTTP 服务

云端的应用或数据库通常仅允许从内网访问。SSH 端口转发通过建立一条加密隧道,让您在本地安全地访问这些服务。

本文介绍如何使用 SSH 本地端口转发 (Local Port Forwarding) 将云端内网服务映射到本地,适用于远程开发、性能测试等场景。

信息

本方案适用于本地或可信网络的临时访问。如果需要向公网用户暴露服务,应使用反向代理或负载均衡器。

适用场景

  • 从本地开发机访问云服务器内网的开发服务
  • 远程调试或测试部署在云端但未向公网暴露的应用
  • 临时访问内网数据库或缓存服务进行性能测试
  • 演示云端应用功能给团队或客户

工作原理

SSH 本地端口转发在您的本地计算机与远程 SSH 服务器之间建立一条加密隧道。您在本地开启一个监听端口,所有发往该端口的流量都被通过 SSH 加密后转发到远程服务器,再由远程服务器转发到目标服务。

text
本地电脑 (localhost:5000)

   [SSH 加密隧道]

开发机 (127.0.0.1:8000)

提示

不仅限于 HTTP 协议

SSH 端口转发支持任何基于 TCP 的协议,包括 MySQL、PostgreSQL、远程桌面等。例如,参考 配置 SSH 端口转发 TCP 服务

信息

反向的使用场景:远程端口转发

本文介绍的是 本地端口转发 (-L),用于将 远程服务 映射到 本地 访问。

相反的场景是 远程端口转发 (-R),用于将 本地服务 暴露给 远程主机 访问。常见用例包括:

  • 将本地代理服务(如 Clash、V2Ray)共享给远程开发机使用
  • 临时让远程主机访问本地的开发服务或资源

详见 共享本地代理服务至远程主机

开始前,请确保满足以下条件:

  • 可通过 SSH 访问的远程开发机(获取 IP 地址、用户名、SSH 端口)
  • 本地电脑已安装 SSH 客户端(macOS 和 Linux 内置;Windows 10+ 已包含)
  • 远程开发机上的目标服务已启动并监听特定端口
  • 本地电脑与开发机之间网络连通
  • (推荐)已配置 SSH 密钥认证,详见 添加 SSH 公钥指南

命令语法

language-bash
ssh -N -f -L <local-port>:127.0.0.1:<target-port> -p <ssh-port> <username>@<tunnel-host>

参数说明

  • -L:启用本地端口转发(必需)
  • -N:不执行远程命令,仅建立隧道
  • -f:在后台运行 SSH 进程
  • <local-port>:本地监听端口(示例:5000
  • 127.0.0.1:目标地址(开发机本地环回地址)
  • <target-port>:开发机上目标服务的端口(示例:8000
  • -p <ssh-port>:开发机的 SSH 端口(示例:-p 45560
  • <username>@<tunnel-host>:开发机的用户名和 IP 地址(示例:root@8.140.242.203

注意127.0.0.1 表示开发机本地环回地址,确保转发到本地运行的服务。

快速开始

  1. 在开发机上启动目标服务:SSH 登录到开发机后,启动您的目标服务。确保服务监听在本地地址(127.0.0.1)的某个端口上。 示例:启动 Python HTTP 服务器

    language-bash
    ssh -p <ssh-port> <username>@<tunnel-host>
    # 在开发机上执行:
    python3 -m http.server 8000 --bind 127.0.0.1
  2. 在开发机上验证服务是否正常运行:

    language-bash
    curl http://127.0.0.1:8000
  3. 建立 SSH 端口转发隧道:在本地电脑打开新的终端,执行以下命令建立端口转发。将占位符替换为实际值。

    language-bash
    ssh -N -f -L <local-port>:127.0.0.1:<target-port> -p <ssh-port> <username>@<tunnel-host>

    完整示例

    假设您的开发机信息如下:

    • IP 地址:8.140.242.203
    • SSH 用户名:root
    • SSH 端口:45560
    • 服务端口:8000
    • 本地转发端口:5000

    执行命令:

    language-bash
    ssh -N -f -L 5000:127.0.0.1:8000 -p 45560 root@8.140.242.203

    如果提示输入密码,请输入开发机的 SSH 密码。命令执行后,隧道在后台运行,无需额外输出。

  4. 在本地电脑上测试转发是否成功 返回本地电脑(不是开发机),进行以下测试:

    方法 A:使用浏览器

    在本地电脑上打开浏览器,访问:

    text
    http://localhost:5000

    您应该看到与 http://127.0.0.1:8000 相同的内容。

    方法 B:使用 curl(推荐)

    在本地电脑的终端运行:

    language-bash
    curl http://localhost:5000

    如果返回了服务的响应,说明隧道建立成功。

    信息

    访问 localhost:<local-port> 时,流量经过 SSH 加密隧道转发到开发机的目标服务。所有通信都受到 SSH 加密保护。

终端录制

通过 SSH 端口转发访问开发机服务

在演示库中打开

适用于临时访问开发机内网 Web 服务,或排查服务是否已正确监听。

关闭端口转发

建立的端口转发在后台运行,使用完毕需要手动关闭,以释放端口和网络资源。

查找转发进程

language-bash
ps aux | grep "ssh -N -f -L"

输出示例:

text
user    1234  0.0  0.0  42532   784 ?        Ss   10:00   0:00 ssh -N -f -L 5000:127.0.0.1:8000 -p 45560 root@8.140.242.203
user    5678  0.0  0.0  12345   678 ?        S+   10:30   0:00 grep ssh -N -f -L

第一行是您创建的 SSH 转发进程,第二列的数字(如 1234)是进程 ID。

终止转发

language-bash
kill <PID>

以上面的例子为例:

language-bash
kill 1234

验证关闭

再次运行查找命令确认进程已终止:

language-bash
ps aux | grep "ssh -N -f -L"

如果只显示 grep 进程本身,说明转发已成功关闭。此时访问 http://localhost:5000 将返回连接错误。

多服务同时转发

如果需要同时转发多个服务,可在一条命令中指定多个 -L 参数:

language-bash
# 同时转发三个服务
ssh -N -f \
  -L 5000:127.0.0.1:8000 \  # Web 应用
  -L 5001:127.0.0.1:3306 \  # MySQL 数据库
  -L 5002:127.0.0.1:5432 \  # PostgreSQL 数据库
  -p 45560 root@8.140.242.203

本地访问方式:

language-bash
curl http://localhost:5000          # 访问 Web 应用
mysql -h 127.0.0.1 -P 5001 -u root # 连接 MySQL
psql -h 127.0.0.1 -p 5002 -U user  # 连接 PostgreSQL

提示

选择本地端口时,避免使用常见端口(如 3306、5432),防止与本地已安装的同类服务冲突。

使用 SSH 配置文件简化命令

为避免每次都输入完整的 IP、用户名、端口和密钥路径,可以通过 SSH 配置文件实现快速转发。

在配置文件中预配置转发(推荐用于固定转发)

编辑 ~/.ssh/config 文件,使用 LocalForward 指令直接配置端口转发:

language-bash
Host dev-server
  HostName 8.140.242.203
  User root
  Port 45560
  IdentityFile ~/.ssh/id_rsa
  LocalForward 5000 127.0.0.1:8000
  ServerAliveInterval 30
  ServerAliveCountMax 3

配置后,只需简单连接即可自动建立转发:

language-bash
ssh -N -f dev-server

优势

  • 无需每次都输入完整的转发参数
  • 配置集中管理,便于维护
  • 支持配置多个 LocalForward 行来转发多个服务

使用 autossh 自动重连

对于长期运行的转发,网络中断会导致隧道断开。autossh 工具可以自动检测和重连,无需手动干预。

安装 autossh

language-bash
# macOS
brew install autossh

# Ubuntu/Debian
sudo apt-get install autossh

# CentOS/RHEL
sudo yum install autossh

使用 autossh

language-bash
# 基本用法:将 ssh 替换为 autossh
autossh -M 5555 -N -f -L 5000:127.0.0.1:8000 -p 45560 root@8.140.242.203

参数说明:

  • -M 5555:监控端口(autossh 用来检测连接健康状态)
  • 其他参数与 SSH 相同

与 SSH 配置结合

如果 SSH 配置文件中已定义了 LocalForward,可使用 autossh 进行自动重连:

language-bash
autossh -M 5555 -N -f dev-server

故障排除

遇到连接问题时,可按照下列流程逐步排查,快速定位问题根源。

故障诊断流程图

text
是否看到错误消息?
├─ "Permission denied"  → [步骤 1:验证认证]
├─ "Connection refused" → [步骤 2:验证远程服务]
├─ "Address already in use" → [步骤 3:检查本地端口]
├─ 无错误但无法访问    → [步骤 4:检查防火墙]
└─ 隧道间歇性断连      → [步骤 5:配置保活]

步骤 1:验证 SSH 认证(Permission denied)

错误信息

text
Permission denied (publickey,password).

原因:用户名、密码或 SSH 密钥错误。

诊断和解决

language-bash
# 测试能否直接 SSH 登录
ssh -p <ssh-port> <username>@<tunnel-host>

# 如果使用密钥,确认密钥存在且权限正确
ls -la ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa

# 明确指定密钥进行转发
ssh -i ~/.ssh/id_rsa -N -f -L <local-port>:127.0.0.1:<target-port> -p <ssh-port> <username>@<tunnel-host>

步骤 2:验证远程服务(Connection refused)

错误信息

text
connect_to localhost port 8000: Connection refused

原因:开发机上的目标服务未启动或端口号错误。

诊断和解决

language-bash
# 登录开发机查看服务是否运行
ssh -p <ssh-port> <username>@<tunnel-host>

# 在开发机上验证服务是否监听指定端口
curl http://127.0.0.1:8000

# 查看所有监听的端口
netstat -tulpn | grep LISTEN
# 或
ss -tulpn | grep LISTEN

步骤 3:检查本地端口(Address already in use)

错误信息

text
bind: Address already in use

原因:本地选择的端口已被其他进程占用。

诊断和解决

language-bash
# 查看哪个进程占用了该端口
lsof -i :5000
# 或
netstat -tulpn | grep 5000

# 选择未被占用的端口重试
ssh -N -f -L 5001:127.0.0.1:8000 -p 45560 root@8.140.242.203

# 或关闭占用该端口的进程
kill $(lsof -ti:5000)

步骤 4:检查防火墙和网络

症状:命令执行无错误,但无法从本地访问服务。

诊断

language-bash
# 验证隧道是否建立成功
ps aux | grep "ssh.*-L" | grep -v grep

# 验证本地端口是否在监听
lsof -i :5000
netstat -tulpn | grep 5000

# 尝试本地连接(在开发机上)
curl http://localhost:5000

可能原因

  • 本地防火墙阻止了连接
  • 云服务商网络策略限制了出站 SSH
  • SSH 隧道建立但目标服务无响应

解决方案

  • 检查本地防火墙设置
  • 检查云服务商的安全组/防火墙规则
  • 在开发机上观察网络日志
language-bash
# 在开发机上实时监听 SSH 连接日志
sudo tail -f /var/log/auth.log | grep sshd

步骤 5:隧道间歇性断连

症状:转发工作一段时间后自动断开连接。

原因

  • 网络路由器/防火墙空闲连接超时
  • SSH 默认心跳检测间隔过长

解决方案

language-bash
# 方案 A:配置 SSH 心跳检测
ssh -N -f -o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
  -L 5000:127.0.0.1:8000 -p 45560 root@8.140.242.203

# 参数说明:
# ServerAliveInterval=30  : 每 30 秒发送一次心跳
# ServerAliveCountMax=3   : 3 次心跳无响应后断开

# 方案 B:使用 autossh 自动重连(推荐用于长期运行)
autossh -M 5555 -N -f -o ServerAliveInterval=30 \
  -L 5000:127.0.0.1:8000 -p 45560 root@8.140.242.203

忘记隧道是否仍在运行

language-bash
# 快速检查特定端口的转发
lsof -ti:5000

# 如果有输出(进程 ID),说明转发仍在运行
# 如果无输出,说明转发已断开

最佳实践

以下建议可以帮助您更安全、更高效地使用 SSH 端口转发。

安全建议

  • SSH 端口转发使用加密隧道,比直接暴露服务更安全
  • 使用 SSH 密钥而非密码进行认证(更强的安全性)
  • 不在互联网上直接暴露开发机的 SSH 端口,仅从可信网络访问
  • 使用完毕后及时关闭转发隧道
  • 使用 -g 标志暴露到网络时,需确保防火墙和网络环境可信

使用建议

  • 为常用的转发配置创建别名 (alias),简化后续操作

    language-bash
    alias tunnel-dev='ssh -N -f -L 5000:127.0.0.1:8000 -p 45560 root@8.140.242.203'
  • 同时转发多个服务时,使用不同的本地端口

  • 记录下使用的端口映射,避免混淆

  • 选择本地端口时,避免常见端口(如 3306、5432)与本地服务冲突

自动化建议

对于长期运行的转发,可考虑:

  • 使用 autossh 自动检测并重连中断的隧道
  • 配置 SSH 配置文件 (~/.ssh/config) 简化命令输入
  • 通过 ServerAliveIntervalServerAliveCountMax 配置心跳保活
  • 通过 systemd 或 launchd 将转发配置为系统服务(高级用法)

相关参考