1. 踩坑背景(Background)
最近在物理服务器上使用 1Panel 面板,通过 Docker 部署了 Gitea 服务。
网页端通过域名的 80/443 端口访问一切正常,但在本地终端使用 Git 推送代码时,却遭遇了连环报错。
起初,报错表现得像是 Git 密钥或权限问题。但顺着网络传输层(Transport Layer)一层层排查后,才发现这是一个非常典型的网络隔离与端口映射问题。
2. 核心结论(TL;DR)
这不是 Git 认证问题,而是纯粹的 TCP 连通性问题。
在使用 Docker 部署非 HTTP 协议的服务时,例如:
- SSH 的
22端口 - MySQL 的
3306端口 - Gitea 自定义的 SSH 端口
如果容器的宿主机端口只绑定在 127.0.0.1,公网直连请求就无法访问该端口。
需要将监听地址改为 0.0.0.0,或者在 Docker 端口映射中直接省略绑定 IP,才能允许外部网络访问。
3. 剥丝抽茧的排错过程(Troubleshooting)
3.1 第一层伪装:权限的错觉
- 现象: 使用默认命令推送时,终端一直卡住或提示输入密码。
- 误区: 以为是本地 SSH 公私钥没有配置正确。
- 破局: 指定自定义的
222端口后,真正的底层网络错误浮出水面。
3.2 第二层阻碍:Connection timed out
明确指定端口后,Git 出现以下报错:
ssh: connect to host example.com port 222: Connection timed out
Connection timed out 表示数据包没有获得任何响应,通常意味着数据包在途中被拦截。
常见原因包括:
- 云服务器安全组未放行端口
- UFW、iptables 等服务器防火墙拦截
- CDN 不支持该 TCP 端口
- 域名经过 Cloudflare 等代理平台
- 运营商或网络出口限制
将域名替换为服务器真实公网 IP 后,成功绕过了超时问题。
这也证明:如果域名启用了 Cloudflare 等 CDN,其默认代理通常只支持部分 Web 端口,不会自动代理 SSH 等普通 TCP 服务。
3.3 第三层铁壁:Connection refused
绕过 CDN 后,错误从超时变成了:
ssh: connect to host 152.32.191.114 port 222: Connection refused
Connection refused 表示数据包已经到达服务器,但目标端口没有正确对外监听。
进入服务器检查 Docker 端口映射后,发现 Gitea 的 SSH 端口被绑定成:
127.0.0.1:222->22/tcp
这意味着:
- 宿主机内部可以访问
222端口 - 公网无法访问
222端口 - 外部请求到达服务器后会被立即拒绝
127.0.0.1 是本地回环地址,只接受服务器内部请求。
3.4 最终破局:解除本地绑定
修改 Docker Compose 配置,将:
ports:
- "127.0.0.1:222:22"
改为:
ports:
- "222:22"
也可以明确写成:
ports:
- "0.0.0.0:222:22"
如果使用 1Panel,也可以在容器端口设置中开启“端口外部访问”。
重建容器后重新测试,Git SSH 连接恢复正常,代码可以正常推送。
4. 核心知识点(Key Takeaways)
4.1 读懂 TCP 的报错语言
Connection timed out
表示数据包在途中没有获得响应。
优先检查:
- 云服务器安全组
- UFW 或 iptables
- CDN 代理状态
- 域名 DNS 解析
- 公网 IP 是否正确
- 运营商网络限制
Connection refused
表示数据包已经到达目标服务器,但目标端口没有服务监听,或者服务没有监听公网网卡。
优先检查:
- 服务进程是否启动
- Docker 容器是否运行
- 端口映射是否正确
- 服务监听地址是否为
0.0.0.0 - 端口是否只绑定在
127.0.0.1
5. 127.0.0.1 与 0.0.0.0 的区别
5.1 127.0.0.1:仅限本机访问
127.0.0.1 是本地回环地址。
绑定该地址意味着:
- 只允许宿主机内部访问
- 不接受公网请求
- 适合数据库、内部 API 等不应暴露到公网的服务
可以将它理解为“闭门运行”。
5.2 0.0.0.0:监听所有网卡
0.0.0.0 表示监听服务器上的所有网络接口。
绑定该地址意味着:
- 可以接受本机请求
- 可以接受局域网请求
- 在防火墙放行后,可以接受公网请求
可以将它理解为“开门监听”。
将端口绑定到
0.0.0.0会扩大服务的网络暴露范围,因此必须同时配置安全组、防火墙和身份认证。
6. 反向代理与客户端直连的区别
6.1 为什么网页端可以正常访问?
因为 1Panel 通常会通过 OpenResty 或 Nginx 监听公网的 80 和 443 端口。
请求流程如下:
浏览器
↓ HTTPS 443
OpenResty / Nginx
↓ 内部转发
Gitea Web 容器
即使 Gitea Web 端口只绑定在 127.0.0.1,Nginx 仍然可以从服务器内部访问它。
6.2 为什么 Git SSH 必须单独开放端口?
Git SSH 使用的是 SSH 协议,不会经过普通的 HTTP 反向代理。
请求流程如下:
本地 Git 客户端
↓ SSH TCP 222
服务器公网端口
↓ Docker 端口映射
Gitea 容器 22 端口
本地终端必须直接访问服务器的 TCP 端口。
如果该端口只绑定在 127.0.0.1,公网客户端就无法建立连接。
7. 排查流程总结
遇到 Docker 服务无法从公网访问时,可以按照以下顺序排查:
- 检查域名是否解析到正确的公网 IP。
- 绕过 CDN,直接使用公网 IP 测试。
- 检查云服务器安全组是否放行端口。
- 检查 UFW、iptables 等本机防火墙。
- 检查 Docker 容器是否正常运行。
- 检查 Docker 端口映射。
- 检查端口绑定的是
127.0.0.1还是0.0.0.0。 - 检查容器内部服务是否真正监听目标端口。
- 最后再排查 SSH 密钥、用户权限和仓库权限。
8. 总结(Summary)
排查网络问题时,不能只盯着应用层配置。
错误表面上可能表现为:
- Git 权限异常
- SSH 密钥无效
- 密码认证失败
- 仓库没有写入权限
但真正的问题可能发生在更底层的 TCP 传输层。
只有结合以下信息进行判断:
- 报错类型
- DNS 与 CDN
- 云服务器安全组
- 系统防火墙
- Docker 端口映射
- 服务监听地址
才能准确定位数据包究竟中断在哪一层。
理解 Connection timed out 与 Connection refused 的区别,以及 127.0.0.1 与 0.0.0.0 的作用,是排查 Docker 网络问题最基础也最重要的一步。