内网服务器无法拉取 Docker 镜像?通过本地代理 + SSH 反向隧道完美解决

一、问题背景

在内网环境中(如公司测试机、家庭实验室),Linux 服务器无法直接访问外网,导致执行 docker pull 时超时失败。而本地 Windows 电脑可以通过代理(如 Clash)正常上网。

目标:让服务器借助本地代理完成镜像拉取,且不暴露代理服务到公网。

最近我在本地搭建了一台 Linux 虚拟机(IP:192.168.126.129),用于 Docker 开发测试。但因为网络环境限制,这台服务器无法直接访问外网,导致执行 docker pull 时总是超时失败:
docker pull hello-world
Error response from daemon: Get “https://registry-1.docker.io/v2/“: net/http: request canceled while waiting for connection

而我的 Windows 主机却可以通过本地代理工具(如 Clash)正常上网,且代理监听在 127.0.0.1:7890

于是我想:能不能让 Linux 服务器“借用”我本机的代理来拉取镜像?

经过一番摸索,最终通过 SSH 反向隧道 + Docker 代理配置 的方式成功解决。整个过程无需在服务器安装代理软件,也不需要开放公网端口,安全又高效。


二、解决思路

既然 Windows 本机可以正常上网(通过本地代理 127.0.0.1:7890),而 Linux 服务器无法访问外网,那么最直接的想法就是:让服务器的流量“穿过”SSH 连接,到达我的 Windows 机器,再由本地代理转发到互联网。

幸运的是,OpenSSH 原生支持一种叫 反向隧道(Reverse Tunnel) 的功能,正好满足这个需求。

📌 核心原理

当我们从 Windows 执行以下命令:
ssh -R 7890:127.0.0.1:7890 root@192.168.126.129

它会在 Linux 服务器上监听 127.0.0.1:7890,并将所有发往该地址的流量,通过 SSH 隧道转发回 Windows 的 127.0.0.1:7890 —— 也就是我的 Clash 代理端口。

这样一来,在服务器看来:
curl http://127.0.0.1:7890/

就等价于在 Windows 上走代理访问外网。

📌 整体流程

  1. Windows 启动 Clash,监听 127.0.0.1:7890
  2. Windows 主动 SSH 连接到 Linux 服务器,并建立反向隧道:-R 7890:127.0.0.1:7890
  3. 在 Linux 上配置 Docker 使用 http://127.0.0.1:7890 作为 HTTP/HTTPS 代理
  4. Docker 拉取镜像时,请求经隧道转发到 Windows,由 Clash 代为访问 Docker Hub

整个过程不暴露代理端口到公网,仅通过已有的 SSH 内网连接完成,安全可靠。


三、前置条件

在动手之前,请确保你的环境满足以下条件:

  • Windows 主机

  • 操作系统:Windows 10/11(已启用 PowerShell)

  • 已配置好本地代理工具(如 Clash、V2RayN 等)

  • 代理监听地址为 127.0.0.1:7890(HTTP/SOCKS5 均可,Docker 只需 HTTP 代理)

  • 建议在代理设置中开启 “允许来自局域网的连接”(即使只走回环,某些系统需要此选项)

  • Linux 服务器

  • 可以是物理机、虚拟机或 WSL2 中的发行版(你使用的是 IP 为 192.168.126.129 的 CentOS/Ubuntu 虚拟机)

  • 已安装并启动 sshd 服务,允许 root 或普通用户通过密码/密钥登录

  • 已安装 Docker(版本不限)

  • 无需能访问外网,也无需安装任何代理软件

  • 网络连通性

  • Windows 能通过内网 IP(如 192.168.126.129)SSH 登录到 Linux 服务器

  • 无需公网 IP,无需端口映射,纯内网即可

💡 注意:本方案不涉及任何公网暴露或第三方中转服务,所有流量均通过你主动建立的 SSH 隧道传输,符合企业内网安全规范。


四、步骤 1:在 Windows 安装 OpenSSH 客户端

Windows 自带的 OpenSSH 功能可能不完整,我们使用官方 GitHub 发布的 Win32-OpenSSH 独立版本,确保包含 ssh.exe 和安装脚本。

1. 下载并解压 OpenSSH

  1. 访问官方仓库:https://github.com/PowerShell/Win32-OpenSSH/releases
  2. 下载最新版的 OpenSSH-Win64.zip(不要下 Portable 版)
  3. 解压到 C:\Program Files\OpenSSH

⚠️ 关键避坑:不要保留顶层文件夹!

  • 正确做法:打开 ZIP 文件 → 全选内部所有文件(包括 ssh.exeinstall-sshd.ps1 等)→ 直接拖入 C:\Program Files\OpenSSH
  • 错误做法:把整个 OpenSSH-Win64 文件夹拖进去,导致路径变成 C:\Program Files\OpenSSH\OpenSSH-Win64\ssh.exe,后续脚本会找不到文件。

2. 以管理员身份运行 PowerShell

Win + X,选择 “Windows PowerShell (管理员)”。

3. 修复系统 PATH(避免 sc.exe 找不到)

安装脚本依赖 sc.exe(位于 C:\Windows\System32),但某些系统 PATH 被篡改,需临时修复:
$env:Path += “;C:\Windows\System32”

4. 运行安装脚本

虽然我们只用客户端,但运行此脚本能正确注册组件并修复权限:
& “C:\Program Files\OpenSSH\install-sshd.ps1”

看到以下输出即表示成功:
sshd and ssh-agent services successfully installed

💡 即使你不需要 sshd 服务,此脚本也会修复 moduli、scp.exe 等文件的 ACL 权限,建议运行。

5. 将 OpenSSH 加入系统 PATH(永久生效)

  1. Win + R,输入 sysdm.cpl,回车
  2. 点击 “高级” → “环境变量”
  3. 在 “系统变量” 中找到 Path,点击 “编辑”
  4. 点击 “新建”,添加:C:\Program Files\OpenSSH
  5. 确定保存,关闭当前 PowerShell,重新打开一个新窗口

6. 验证安装

在新 PowerShell 中执行:
ssh -V

应返回类似:
OpenSSH_for_Windows_10.0p2 Win32-OpenSSH-GitHub, LibreSSL 4.2.0

✅ 至此,OpenSSH 客户端已准备就绪。


五、步骤 2:配置反向 SSH 隧道

现在,我们要从 Windows 主动连接 Linux 服务器,并建立一条“反向隧道”,将服务器的 127.0.0.1:7890 映射到 Windows 本地的代理端口。

1. 启动 Clash 并确认监听地址

确保你的代理工具(如 Clash)正在运行,且 HTTP 代理监听在:
127.0.0.1:7890

💡 建议在 Clash 设置中开启 “允许来自局域网的连接”,即使只走回环地址,某些 Windows 版本或防火墙策略需要此选项才能让隧道流量通过。

2. 建立反向隧道

打开 新的 PowerShell 窗口(无需管理员),执行以下命令:
ssh -R 7890:127.0.0.1:7890 root@192.168.126.129

📌 参数说明:-R 7890:127.0.0.1:7890 表示:
在远程服务器(192.168.126.129)上监听 127.0.0.1:7890,所有发往该端口的流量,将通过 SSH 隧道转发到 本机(Windows)127.0.0.1:7890

3. 首次连接处理

如果是第一次连接,会看到类似提示:
The authenticity of host ‘192.168.126.129 (192.168.126.129)’ can’t be established.
ED25519 key fingerprint is SHA256:xxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

输入 yes 并回车,SSH 会将该主机加入 known_hosts 列表。

4. 登录服务器

接着输入服务器的 root 密码(或其他用户密码):
root@192.168.126.129‘s password:

成功后,你会看到类似:
Last login: Sat Dec 20 12:56:40 2025 from 192.168.126.1
[root@docker ~]#

5. ⚠️ 关键:保持此窗口开启!

不要关闭这个 PowerShell 窗口!
一旦关闭,SSH 连接断开,反向隧道也随之失效,Docker 将无法再通过代理拉取镜像。

💡 小技巧:你可以把这个窗口最小化到任务栏,或者写一个批处理脚本(.bat)一键启动隧道,方便后续使用。

6. 验证隧道是否生效(可选)

在服务器上临时测试(在 [root@docker ~]# 提示符下):
curl -x http://127.0.0.1:7890 https://www.baidu.com –connect-timeout 5

如果返回网页内容,说明隧道和代理均工作正常。


六、步骤 3:在 Linux 服务器配置 Docker 代理

现在 SSH 隧道已经建立,服务器上的 127.0.0.1:7890 实际指向你的 Windows 本地代理。接下来,我们要让 Docker 使用这个地址作为 HTTP/HTTPS 代理。

1. 创建 systemd 代理配置目录

以 root 身份执行(你已通过 SSH 登录为 root):
mkdir -p /etc/systemd/system/docker.service.d

💡 这是 systemd 推荐的覆盖配置方式,不会修改原始 docker.service 文件,便于维护和升级。

2. 创建代理配置文件

使用 cat 命令快速写入配置:
cat > /etc/systemd/system/docker.service.d/proxy.conf <<EOF
[Service]
Environment=”HTTP_PROXY=http://127.0.0.1:7890
Environment=”HTTPS_PROXY=http://127.0.0.1:7890
Environment=”NO_PROXY=localhost,127.0.0.1,.local,.internal”
EOF

3. 配置项说明

  • HTTP_PROXY / HTTPS_PROXY:指定代理地址,必须使用 http:// 协议前缀(即使底层是 SOCKS5,Docker 只认 HTTP 代理)

  • NO_PROXY:避免本地流量被代理,提升性能并防止环路
    常见值包括:

  • localhost127.0.0.1:本机回环

  • .local.internal:局域网域名

  • 你自己的私有仓库地址(如 registry.local)也可加入

4. 重载配置并重启 Docker

执行以下命令使配置生效:
systemctl daemon-reload
systemctl restart docker

⚠️ 注意:必须执行 daemon-reload,否则 systemd 不会加载新的 .conf 文件。

5. 验证 Docker 是否加载了代理

运行以下命令检查环境变量是否注入成功:
systemctl show docker | grep -i proxy

应看到输出包含:
HTTP_PROXY=http://127.0.0.1:7890
HTTPS_PROXY=http://127.0.0.1:7890

✅ 表示配置已生效。


七、验证是否成功

配置完成后,我们需要分两步验证:先测试代理连通性,再测试 Docker 拉取镜像。

1. 测试代理是否可达(可选但推荐)

在服务器的终端([root@docker ~]#)执行:
curl -x http://127.0.0.1:7890 https://www.google.com –connect-timeout 5

如果返回 HTML 内容或 Google 首页,说明代理通道正常。

💡 你也可以直接测试 Docker Hub 的 API 端点:
curl -x http://127.0.0.1:7890 https://registry-1.docker.io/v2/

正常会返回 {"errors":[{"code":"UNAUTHORIZED",...}]} —— 这是 预期行为
因为公开镜像拉取时,Docker 客户端会自动处理匿名认证,401 不代表失败。

2. 拉取测试镜像

执行经典测试命令:
docker pull hello-world

如果看到类似以下输出,说明大功告成:
Using default tag: latest
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
Digest: sha256:d4aaab6242e0cace87e2ec17a2ed3d779d18fbfd03042ea58f2995626396a274
Status: Downloaded newer image for hello-world:latest

3. 验证镜像可用性

运行容器确认镜像完整:
docker run –rm hello-world

应输出:
Hello from Docker!
This message shows that your installation appears to be working correctly.

🎉 恭喜!你的内网服务器现在可以通过本地 Windows 代理正常拉取任何公开 Docker 镜像了!


八、常见坑与排查

1. PowerShell 报错:“ssh 无法识别为 cmdlet”

现象
ssh : 无法将“ssh”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

原因:未将 C:\Program Files\OpenSSH 加入系统 PATH,且 PowerShell 默认不加载当前目录程序。

解决

  • 临时方案:用完整路径调用
    & “C:\Program Files\OpenSSH\ssh.exe” -V

  • 永久方案:将 C:\Program Files\OpenSSH 加入系统环境变量 PATH,并重启 PowerShell

2. 运行 install-sshd.ps1 时报错找不到 sc.exe

现象
sc : 无法将“sc”项识别为 cmdlet…

原因C:\Windows\System32 不在当前会话的 PATH 中(某些精简版系统或安全软件会修改 PATH)。

解决:在执行安装脚本前,先临时添加路径:
$env:Path += “;C:\Windows\System32”
& “C:\Program Files\OpenSSH\install-sshd.ps1”

3. 解压后 ssh.exe 路径不对(多了一层文件夹)

现象:执行 ssh -V 失败,或脚本报错找不到文件。

原因:ZIP 解压时保留了顶层文件夹(如 OpenSSH-Win64),导致实际路径为:
C:\Program Files\OpenSSH\OpenSSH-Win64\ssh.exe

解决

  • 删除 C:\Program Files\OpenSSH 整个目录
  • 重新解压:打开 ZIP → 全选内部所有文件 → 直接拖入 C:\Program Files\OpenSSH
  • 确保 ssh.exe 直接位于该目录下

4. Docker 仍无法拉取镜像(超时)

排查步骤

  1. ✅ 确认 SSH 隧道窗口未关闭(这是最常见原因!)

  2. ✅ 确认 Clash 正在运行,且监听 127.0.0.1:7890

  3. ✅ 在 Clash 设置中开启 “允许来自局域网的连接”

  4. ✅ 在服务器上测试代理:
    curl -x http://127.0.0.1:7890 https://www.baidu.com

  5. ✅ 检查 Docker 是否加载代理:
    systemctl show docker | grep -i proxy

  6. ✅ 确保执行过 systemctl daemon-reload(否则配置不生效)

5. 首次连接 SSH 提示指纹,但输 yes 后卡住

可能原因:网络延迟或服务器负载高。
建议:耐心等待几秒,或按回车尝试唤醒输入。若持续失败,检查防火墙是否放行 22 端口。

6. 想免密登录?配置 SSH 公钥

避免每次输密码,可在 Windows 生成密钥并复制到服务器:
# 在 Windows PowerShell 中
ssh-keygen -t ed25519
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@192.168.126.129

之后即可直接连接,无需密码。


九、总结

通过 SSH 反向隧道 + Docker 代理配置,我们成功让内网 Linux 服务器“借用”了 Windows 本机的代理能力,实现了无公网暴露的 Docker 镜像拉取。

  • 安全:所有流量均通过 SSH 隧道加密传输,不暴露代理端口到公网。
  • 高效:无需在服务器安装额外软件,仅利用现有 SSH 连接即可。
  • 灵活:可随时开启/关闭隧道,适配不同场景下的内网开发需求。

这种方案不仅适用于 Docker 镜像拉取,也可推广到其他需要外网访问的场景(如 apt updatepip install 等),是内网环境下的通用解决方案。