适合读者:能登录 Linux 服务器,但遇到“网站打不开”时还缺少固定排查顺序的初级运维人员。本文围绕普通 HTTPS over TCP 请求展开;示例中的 example.test 域名和 192.0.2.0/24 地址用于文档演示,实际排查需要替换。

1. Ping 通了,为什么网页仍然打不开
Ping 成功,说明某条路径上的 ICMP 请求得到了回应。浏览器访问 HTTPS,还要完成域名解析、目标端口连接、TLS 握手、HTTP 请求和应用处理。任何一环出问题,都可能显示“打不开”。
同样,Ping 被禁也不等于网站不可用。要围绕出问题的业务请求收集证据,而不是用一种探测结果替代整个访问链路。
先记录四件事:访问的完整 URL、发生时间、客户端所在网络、浏览器或程序的原始错误。这些信息能避免排查过程中悄悄换了域名、端口或请求路径。
2. 用 curl 看清请求停在哪一步
先对真实 URL 做一次带时间限制的请求:
curl -v --connect-timeout 3 --max-time 10 \
https://app.example.test/healthz
-v 会显示连接、TLS 和 HTTP 细节;--connect-timeout 限制连接阶段,--max-time 限制整个操作。连接阶段也可能包含 DNS 和 TLS,因此“连接超时”还需要结合详细输出判断。参数含义见 curl 官方手册。
分享输出前检查其中是否有 Authorization、Cookie 或内部地址。排障记录保留必要字段即可。
可以重点找这些线索:
| 输出线索 | 当前定位 | 优先检查 |
|---|---|---|
Could not resolve host |
名称解析失败 | 拼写、系统解析配置、DNS 或代理主机 |
Connection refused |
连接被拒绝 | 目标地址、端口、监听或显式拒绝策略 |
Connection timed out |
某阶段未在期限内完成 | 详细输出最后停在哪、网络路径和服务响应 |
certificate verify failed 等证书错误 |
TLS 校验失败 | 域名、有效期、证书链、信任和系统时间 |
| 已收到 HTTP 状态码 | 请求已到达某个 HTTP 响应方 | 状态码、正文、响应头和对应日志 |
“Connection refused”经常来自端口无人监听,但也可能是中间设备主动拒绝,不能直接断言服务进程一定停止。
3. 第一层:域名是否解析到预期地址
在出问题的客户端执行:
getent ahosts app.example.test
getent 使用系统名称服务配置,因此结果可能受 /etc/hosts、DNS 等共同影响。安装了 dig 时,还可以单独观察 DNS 响应:
dig app.example.test A
dig app.example.test AAAA
比较地址是否符合当前架构:访问 CDN 时可能得到边缘节点地址,内网域名在公网解析失败也可能是预期行为。不要仅因为解析结果不是源站 IP,就判定 DNS 出错。
若只在部分客户端失败,分别比较内外网解析、IPv4 和 IPv6 路径:
curl -4 -v --max-time 10 https://app.example.test/healthz
curl -6 -v --max-time 10 https://app.example.test/healthz
IPv6 请求失败还可能只是本机没有可用 IPv6。需要把地址记录和实际连接能力一起看。
4. 第二层:业务端口和监听地址是否正确
在目标 Linux 服务器上查看 TCP 监听:
sudo ss -lntp
ip -br address
ip route
监听 127.0.0.1:8080 的程序只接受对应网络命名空间内的本机连接。它可能被设计为由 Nginx 反向代理访问,并不一定要对所有地址监听。
排查时画清楚实际路径,例如“客户端 → 负载均衡 443 → Nginx 443 → 应用 8080”。外部请求失败时,应分别验证每一跳的目标地址和端口。
如果 Nginx 在容器里,容器内的 127.0.0.1 指向该容器,和宿主机的回环地址不是一处。探测必须在能代表真实请求来源的环境里执行。
5. 第三层:TLS 校验为什么失败
TLS 问题常见于四个方面:证书里的域名不匹配、证书过期或尚未生效、中间证书链不完整、客户端不信任签发方。系统时间错误也会影响有效期判断。
在客户端检查时间,然后看 curl 的具体证书错误:
date -Is
curl -v --max-time 10 https://app.example.test/healthz
需要测试某个指定服务器 IP,同时保留真实域名时,可以使用:
curl --resolve app.example.test:443:192.0.2.10 \
--connect-timeout 3 --max-time 10 -v \
https://app.example.test/healthz
把域名、端口、IP 换成真实值。这样 URL 中的主机名仍用于 TLS 的 SNI、证书名称校验及 HTTP Host,而连接地址由本次命令指定。只修改 Host 请求头不能完整替代这件事。
--resolve 适用于本次直接连接测试;存在显式代理时,应结合代理的解析和连接方式判断。curl 对这些机制的说明见 Everything curl:Name resolve tricks。
使用 -k 会跳过关键的证书校验。即使临时使用它后请求成功,也只能说明校验值得进一步检查,不能把该结果作为证书问题已经修复的证据。
6. 第四层:收到 404 或 502,其实已经多了一条证据
收到 HTTP 响应后,要问“是谁返回了它”,再解释状态:
404:该响应方没有找到相应资源,可能是路径、虚拟主机或路由问题。401/403:检查身份、权限和访问策略。502/503/504:常见于代理上游异常、服务不可用或等待超时,但具体含义取决于响应方。
响应头和页面样式能提供线索,最终仍需要结合入口代理和应用日志。不能看到 502 就断言数据库故障。
curl 默认也不会把所有 HTTP 4xx/5xx 自动当作命令失败。脚本如果需要这类响应返回非零退出码,可使用 --fail;需要同时保留错误正文时,较新版本可使用 --fail-with-body。具体行为见 curl HTTP 失败处理说明。
7. 本地实验:亲眼看到“连接成功、业务请求失败”
使用编辑器保存下面的 http-demo.py,在装有 Python 3 的测试环境运行。服务只监听本机回环地址:
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
ok = self.path == "/healthz"
body = b"ok\n" if ok else b"not found\n"
self.send_response(200 if ok else 404)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 18081), Handler).serve_forever()
终端 A 启动服务:
python3 http-demo.py
终端 B 分别访问有效路径和不存在的路径:
curl --max-time 5 -i http://127.0.0.1:18081/healthz
curl --max-time 5 -i http://127.0.0.1:18081/missing
printf 'curl_exit=%s\n' "$?"
预期第一个请求是 200,第二个请求是 404,但第二条 curl 命令本身的退出码仍为 0:它成功完成了 HTTP 传输。
再明确要求 curl 将 HTTP 错误视为失败:
curl --fail --max-time 5 http://127.0.0.1:18081/missing
printf 'curl_exit=%s\n' "$?"
预期退出码为 22。退出码要立即读取,中间插入别的命令会覆盖 $?。实验结束后,在终端 A 按 Ctrl+C 停止服务。
8. 修复后如何证明链路恢复
分别在服务器本机和真实客户端复测原始 URL,记录 HTTP 状态、连接到的地址和耗时:
curl -sS --connect-timeout 3 --max-time 10 \
-o /dev/null \
-w 'http=%{http_code} remote=%{remote_ip} total=%{time_total}s\n' \
https://app.example.test/healthz
还要验证实际业务页面、身份认证和必要的接口。健康检查返回 200,只能证明该检查定义覆盖的功能。
有效的排查记录通常能写成一句明确的话:“域名解析正确、TCP 连接成功、TLS 校验通过,请求在入口返回 404;修正虚拟主机路由后,原始 URL 从内外两个位置均返回预期内容。”这样下一次类似故障,就有了可对照的证据。