/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
Docker 容器明明 Up,映射也写了 -p 8080:8080,可浏览器或 curl 就是连不上——connection refused 和 timeout 这两种报错背后,往往是完全不同的故障层。更坑的是容器日志一切正常,人很容易卡在“重启容器”这个无效动作上反复打转。
只看 docker ps 不够用:它只能证明 Docker 写好了映射,证明不了宿主机端口真的在监听、服务真的绑了 0.0.0.0、防火墙真的放行了。链路上任何一环断掉,表现都一样是“访问不了”。
本文分享一套按层收敛的五步排障法:容器内自证 → 映射核对 → 绑定地址 → 防火墙放行 → 脚本固化,每步一条命令定性,可直接落地。
一、现象分流:先定位故障在哪一层
动手前先做一次现象分流,三类表现指向的故障层完全不同:
- 容器内也不通:说明服务没监听或端口写错,跟 Docker 网络无关,别急着改 -p。
- 容器内通、宿主机不通:问题收敛在端口映射写法或监听地址上。
- 宿主机通、外部不通:基本可以锁定防火墙或云安全组没放行。
服务监听 → 容器网络命名空间 → 端口映射 → 宿主机监听 → 防火墙/安全组 → 客户端
| 容器内 curl 也不通 | 应用未监听 / 端口写错 | docker exec <容器> curl -s localhost:8080 |
| 容器内通,宿主机 127.0.0.1 不通 | 映射缺失或写反 | docker ps 看 PORTS 列 |
| 宿主机通,外网不通 | 防火墙 / 安全组 | curl http://服务器公网IP:8080 |
核心结论:先分层再动手,能省掉一半无效重启。
二、第一步:核对端口映射与宿主机监听
确认 Docker 到底把端口交给了谁,两条命令即可定性:
# 在宿主机执行,确认映射与监听状态
docker ps –filter name=your-app –format 'table {{.Names}}\\t{{.Ports}}'
ss -lntp | grep -E ':8080\\b'
- PORTS 列为空:容器没做映射,或写成了裸 8080,只有 0.0.0.0:8080->8080/tcp 才算成功。
- ss 显示 127.0.0.1:8080:外部流量进不来,需改成 0.0.0.0 或 ::。
- ss 根本没有该端口:映射成功但容器内服务没起,回到第三步排查。
映射检查双凭证:PORTS 列有 ->、ss 有 0.0.0.0 监听,缺一不可

三、第二步:钻进容器自证服务可用
排除宿主机干扰,直接在容器内部发一次请求:
# 容器内自测,适配有 curl 的镜像
docker exec -it your-app sh -c 'curl -sS -o /dev/null -w "%{http_code}\\n" http://127.0.0.1:8080/health'
- 返回 200:服务健康,故障一定在容器外,直接跳到第四步。
- 返回 000 或 connection refused:容器内没人监听,检查启动参数、端口变量、健康检查配置。
- 镜像里没有 curl:改用 bash /dev/tcp/127.0.0.1/8080 或装 netcat 复测。
容器内自证是分水岭:通了就别折腾镜像,不通就别折腾防火墙
四、第三步:盯紧监听地址是回环还是全网卡
这是最容易被忽略的一环,监听地址决定谁能连进来:
# 同时探测回环地址与宿主机网卡地址
curl -sS -m 3 http://127.0.0.1:8080/health && echo LOCAL_OK
curl -sS -m 3 http://$(hostname -I | awk '{print $1}'):8080/health && echo ETH_OK
- LOCAL_OK 而 ETH_OK 失败:服务只绑了 127.0.0.1,外部流量自然打不进来。
- 两个都失败:端口压根没起来,回到第二步的 ss 复查。
- 框架常见坑:Spring Boot 的 server.address=127.0.0.1、Node 的 –host localhost,都要改成 0.0.0.0。
127.0.0.1 是只给本机的门,0.0.0.0 才是给所有人的门

五、第四步:防火墙与安全组三层放行核对
前面都通了还连不上,就逐层查三处拦截:
| RHEL/CentOS | firewalld | firewall-cmd –list-ports | –add-port=8080/tcp –permanent 后 –reload |
| Ubuntu | ufw | ufw status verbose | ufw allow 8080/tcp |
| 裸规则 | iptables | iptables -L INPUT -n | -A INPUT -p tcp –dport 8080 -j ACCEPT |
| 云主机 | 安全组 | 控制台入站规则 | 放行 TCP 8080 并限制来源 IP |
# firewalld 一次性放行并回读验证
sudo firewall-cmd –permanent –add-port=8080/tcp && sudo firewall-cmd –reload
sudo firewall-cmd –list-ports
- 云主机最容易漏:机器有公网 IP 时,安全组是第一道门,系统里放行了也白搭。
- 顺序原则:先 OS 防火墙、后安全组,一次只关一层复测,才能定位到底是谁在拦。
放行要一层一层验,别一次全关导致分不清拦截来源
六、第五步:把五步固化成一键复测脚本
排障动作重复太多次,直接写成脚本一次跑完:
#!/usr/bin/env bash
# 五步一键复测:适配 Linux 宿主机,替换容器名与端口即可
set -uo pipefail
C=your-app; P=8080
echo "[1] 映射:"; docker ps –filter name=$C –format '{{.Ports}}'
echo "[2] 容器内:"; docker exec $C sh -c "curl -s -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:$P/ || echo FAIL"
echo "[3] 宿主机:"; curl -sS -m 3 -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:$P/ || echo FAIL
echo "[4] 网卡:"; curl -sS -m 3 -o /dev/null -w '%{http_code}\\n' http://$(hostname -I | awk '{print $1}'):$P/ || echo FAIL
echo "[5] 防火墙:"; (firewall-cmd –list-ports 2>/dev/null || ufw status 2>/dev/null || echo "无 firewalld/ufw")
- 哪一行先失败:故障层就在该行对应的环节,不必再从头猜。
- 接入 CI:把脚本放进部署流水线,容器 Up 但接口不通时直接红灯拦截。
脚本化的本质是把五层链路变成可重复的回归检查

结语
这套五步法的价值在于把“容器起来了但接口不通”这个黑盒问题,拆成了五段可独立验证的链路:每一层都有明确的检查命令和判定标准,排障不再靠重启碰运气。日常把复测脚本挂进部署流程,还能在问题影响业务之前提前拦截。
记住顺序:先证明服务活着,再证明映射存在,最后才谈防火墙——链路思维比任何单条命令都重要。





