先给结论:绝大多数情况下,网络不通不会显示 404。这个状态码出现的时候,通常说明你的请求已经成功传到了服务器,服务器也正常处理了,但它找不到你要的那个资源。我遇到过很多朋友第一反应就是去查防火墙、查专线、ping 网关,结果折腾半天,最后发现只是路径写少了一层,或者 Nginx 少写了一行 try_files。
这篇文章不是单纯解释一个状态码,而是想把 404 和“网络通不通”这条线彻底理清楚。我会从 HTTP 请求的完整链路讲起,再结合 Nginx 刷新 404、Tomcat 启动后 404、conda 源 404、torchvision 下载 MNIST 报 404、大模型接口返回 404 这些真实场景,最后给你一套 5 分钟上手排查的方法。
1. 搞清楚 404 在网络请求链路中的位置
1.1 一次 HTTP 请求要过多少关
一个请求从你输入地址到看到结果,要经过这些环节:DNS 解析、TCP 三次握手、TLS 握手、发送 HTTP 请求、服务端路由匹配、业务逻辑处理、返回响应。你可以把这个流程想象成寄快递:DNS 是查目的地地图,TCP 是确认收货地址有没有人,TLS 是进小区刷卡,HTTP 请求是快递单本身,404 就是快递已经送到小区但前台告诉你没这个人。
断网通常发生在中间靠前的环节:DNS 解析失败、TCP 连不上、TLS 证书校验不过。这些都发生在真正发出 HTTP 请求之前。服务器根本没有收到请求,自然不可能回一个 404。所以一个原生的 404 响应,本身就说明网络链路已经走通了,至少 TCP 连接已经建立。
很多人会混淆“服务器无响应”和“服务器响应了但找不到”,这两个是完全不同的场景。前者是网络层或连接层的问题,后者是应用层的问题。判断的关键就是看有没有一个 HTTP 状态码回来。
1.2 404 是“服务器收到了,但没找到”
HTTP 状态码按首位数字分成几类:2xx 表示成功,3xx 表示重定向,4xx 表示客户端的问题,5xx 表示服务端的问题。404 属于 4xx,准确定义是“服务器理解了请求,但找不到对应资源”。
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 200 | 请求成功 | 页面、接口、文件都能正常返回 |
| 301/302 | 重定向 | 地址变了,服务器让你跳转 |
| 400 | 请求语法错误 | 参数格式不对,body 不是合法 JSON |
| 401 | 未认证 | 没带 token,或 token 过期 |
| 403 | 禁止访问 | 没权限,但资源存在 |
| 404 | 资源不存在 | 路径写错、文件被删、路由不匹配 |
| 500 | 服务器内部错误 | 代码抛异常,服务处理不了 |
| 502/504 | 网关或上游异常 | 后端服务挂了或响应超时 |
注意,404 可以由源站返回,也可以由中间的网关、负载均衡器返回。但无论如何,它都是“有东西在 HTTP 层回话了”。如果网络真断了,你看到的不会是 404,而是 timeout、connection refused、could not resolve host 这一类错误。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 网络不通时你看到的到底是什么
2.1 浏览器和 curl 的真实报错长什么样
网络不通时,浏览器和 curl 会给出比较明确的错误提示。我把最常见的几种列出来,方便你对照。
| 故障点 | 浏览器提示 | curl 提示 |
|---|---|---|
| DNS 解析失败 | ERR_NAME_NOT_RESOLVED |
Could not resolve host |
| 目标端口没服务 | ERR_CONNECTION_REFUSED |
Connection refused |
| 连接超时 | ERR_CONNECTION_TIMED_OUT |
Operation timed out |
| TLS 证书校验失败 | ERR_CERT_AUTHORITY_INVALID |
SSL certificate problem |
| 连接被重置 | ERR_CONNECTION_RESET |
Connection reset by peer |
这些提示不是应用服务器生成的,而是操作系统、浏览器或者 curl 自己判断出来的。它们的特征是没有状态码,或者页面直接显示“无法访问此网站”。如果你在浏览器开发者工具的 Network 面板里看到的请求状态是 (failed),那大概率是网络问题,而不是 404。
2.2 为什么“断网”时偶尔会看到 404
这个问题值得拆开讲。第一种情况是:你所谓的“断网”不是绝对断网。比如电脑连不上外网,但本机还在跑 Tomcat 或 Nginx,你通过 localhost 或内网 IP 去访问,TCP 连接能建立,HTTP 请求能到本机服务。这时如果路径写错,照样返回 404。这属于“外网断了但本地环回没断”,不是服务器在网络不通时还能回 404。
第二种情况是前端有 Service Worker。有些前端项目注册了 Service Worker,实现离线缓存。离线状态下,如果请求的资源不在缓存里,Service Worker 可以自己生成一个 404 响应返回给页面。这时候浏览器确实会显示 404,但响应根本不是源站产生的。
第三种情况是中间层做了缓存或转发。比如某个网关缓存了一个“资源不存在”的响应,你后续的请求即使断了网,也可能从缓存层拿到 404。这种属于缓存策略导致的现象,不代表服务器真的收到了你的请求。
所以“网络不通会显示 404 吗”这个问题的答案不是绝对的不,真实世界里存在边界情况。但从排查角度来说,第一反应永远是:看到 404,先假定网络是通的。
3. 最容易和“网络不通”混在一起的 404 实例
3.1 Nginx 部署若依前端,刷新就 404
若依(RuoYi)这类前后端分离项目,前端大部分用的是 Vue Router,而且一般是 history 模式。部署到 Nginx 之后,你从首页点进去,页面跳转和路由切换都很正常,但一旦按 F5 刷新,或者直接访问 /system/user 这样的地址,Nginx 就会返回 404。
原因很简单:前端路由切换只是在浏览器里改了 URL,没有向服务器发新请求。但刷新浏览器时,浏览器会按当前地址栏里的路径去请求 Nginx,比如请求 /system/user。Nginx 在静态目录里找不到叫 system/user 的文件,自然就回了 404。这不是网络问题,甚至不是前端代码问题,而是 Nginx 配置少了一行。
nginx复制server {
listen 80;
server_name example.com;
root /opt/ruoyi/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
关键就是 try_files $uri $uri/ /index.html;。它的意思是:先找真实文件,找不到就交给 /index.html 去处理。这样前端路由就能接管路径,刷新时也不会 404。
3.2 Tomcat 启动后访问 404
另一个高频场景是:Tomcat 启动了,端口也监听了,但打开 http://localhost:8080/ 一看,404。很多人的第一反应是“是不是网络不通”“是不是端口没起来”。实际上端口是通的,Tomcat 也响应了,问题出在应用部署上。
常见原因有几个:一是 Tomcat 的 webapps 目录下没有 ROOT 应用,访问 / 当然没有默认页面;二是你把项目打成了 myapp.war,但没有访问 http://localhost:8080/myapp/;三是 IDE 里启动时没有执行 deploy,应用根本没进 webapps;四是项目的欢迎文件不是 index.html,或者 web.xml 里没有配置 welcome file。
排查顺序应该是:
bash复制# 查看 webapps 下有什么
ls -l $CATALINA_HOME/webapps
# 看启动日志
tail -f $CATALINA_HOME/logs/catalina.out
# 看应用自己的日志
tail -f $CATALINA_HOME/logs/localhost.$(date +%Y-%m-%d).log
如果 webapps 下没有 .war 或解压目录,说明应用没有部署成功。如果部署了,访问时要用对应的 context path。比如包名是 myapp.war,访问地址就是 http://localhost:8080/myapp/,而不是根路径。
3.3 torchvision 下载 MNIST 报 404
Python 生态里也有典型例子。很多人第一次跑深度学习代码,用 torchvision.datasets.MNIST 下载数据集,结果报错:
text复制HTTP Error 404: Not Found
这个报错看着很像网络问题,但实际是数据集的下载源路径发生了变化,或者官方服务器上对应的文件已经不在原来位置。请求确实发送出去了,文件服务器也收到了,但返回的是“文件不存在”。网络是通的,问题在 URL。
最稳妥的办法是手动下载四个文件,放到指定目录,然后让 torchvision 直接读取:
text复制data/
MNIST/
raw/
train-images-idx3-ubyte.gz
train-labels-idx1-ubyte.gz
t10k-images-idx3-ubyte.gz
t10k-labels-idx1-ubyte.gz
代码里把 download 设为 False:
python复制from torchvision import datasets
mnist = datasets.MNIST(root="./data", train=True, download=False)
如果你用的是新版 torchvision,它内置的数据集 URL 可能已经指向新地址。遇到 404 时,先别急着换网络,去官方仓库看看 URL 是否更新了,或者找一个稳定可用的镜像地址,手动补全文件。
3.4 conda 安装包报 HTTP 404
conda 用户应该都见过这类报错:
text复制UnavailableInvalidChannel: HTTP 404 NOT FOUND for channel anaconda/pkgs/msys
看到 HTTP 404,有人会怀疑是网络断了,或者源连不上。其实 conda 能拿到 HTTP 404,说明它已经成功访问到了服务器,只是服务器上不存在对应的 channel 路径。常见原因是配置的 channel 列表里有不存在的地址,或者是某个镜像没有同步对应的子目录。
先查看当前配置:
bash复制conda config --show channels
如果发现异常或多余的 channel,可以重置:
bash复制conda config --remove-key channels
然后写一个干净的 .condarc:
yaml复制channels:
- conda-forge
- defaults
show_channel_urls: true
安装包时也可以临时覆盖 channel:
bash复制conda install --override-channels -c conda-forge numpy
这样 conda 就不会去请求 anaconda/pkgs/msys 这个不存在的路径了。排查时可以直接用 curl 请求报错里给出的完整 URL,如果返回 404,就说明是 channel 路径问题,不是网络问题。
3.5 大模型 API 返回 404:模型不存在
调用大模型开放平台接口时,也经常遇到这种返回:
text复制unexpected status 404 not found: {"error":"model not found"}
这个报错已经非常明确地告诉你:HTTP 链路是通的,服务端也正常响应了,但请求体里的模型名不对。通常有三个原因:模型 ID 拼写错误;模型版本后缀不对;接口路径里的版本号和所用模型不匹配。
比如某个平台要求请求 /api/v1/chat/completions,你写成了 /api/chat/completions;或者文档里模型叫 glm-4-flash,你在代码里写成了 glm4-flash。服务端查不到这个模型,自然回 404。
调试时用 curl 看完整请求:
bash复制curl -v https://open.bigmodel.cn/api/paas/v4/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"glm-4-flash","messages":[{"role":"user","content":"hello"}]}'
重点看返回的 model not found 是不是跟着你传的模型名走。还有一些平台出于安全考虑,会在模型名不存在时故意用 404 而不是 403,防止客户端探测可用模型。这时更应该先去查文档确认准确 ID。
3.6 eNSP 启动 AR201 报 404
如果你在华为 eNSP 这种本地模拟器里启动设备时见到 404,很容易懵:我本地跑软件,怎么还报 HTTP 状态码?其实这种 404 和网络没任何关系,更像是一种“资源找不到”的本地错误提示。
以 AR201 为例,常见原因是安装包不完整或组件缺失,启动时系统找不到对应的设备镜像文件。还有一个高频坑是安装目录被移动过,或者从别人那里拷贝了绿色版,设备组件路径失效。
处理思路是:检查完整版安装包和兼容性;以管理员身份启动 eNSP;重新注册或安装设备包;如果还不行,就彻底卸载重装完整版。遇到这类 404,不用去 ping 网络,重点检查本地文件。
3.7 构建任务和远程组件返回 404
还有一些分布式构建或远程任务工具,报错长这样:
text复制error running remote compact task: unexpected status 404 not found: {"detail":"..."}
返回体里带 detail 字段,通常是某个远程服务已经把请求接到了,但根据任务 ID、组件版本或产物路径找不到内容。常见原因是任务记录被清理了,或者远端服务升级后旧版本文件不再兼容。
排查方向是:确认任务 ID 是否存在、版本号是否被更新、远端服务日志里有没有对应的处理记录。这不是“网络不通”的报错,因为网络已经完成了它的工作。
4. 5 分钟定位:到底是网络问题还是 404 问题
4.1 先看请求有没有到服务器
排查这种问题,我习惯按这个顺序来。
第一,看浏览器开发者工具的 Network 面板。如果请求状态码写的是 404,说明响应已经回来了,网络链路没问题。如果请求状态是 (failed),并且错误是 ERR_CONNECTION_REFUSED 或 ERR_NAME_NOT_RESOLVED,再往网络层排查。
第二,用 curl -v 看完整过程:
bash复制curl -v https://example.com/api/user/123
如果输出里有 Connected to example.com,并且最后能看到 < HTTP/1.1 404,那网络是通的,服务器也确实响应了。如果卡在 Trying 或 Could not resolve host,才算网络问题。
第三,看服务端 access log。Nginx 的 access log、Tomcat 的 localhost log、应用框架的访问日志,只要出现这条请求的 404 记录,就证明服务器收到了请求。如果日志里根本没有这条请求,那才需要考虑是不是请求被网络层拦截了。
4.2 常用排查命令速查表
| 检查点 | 命令 | 说明 |
|---|---|---|
| DNS 解析 | dig example.com 或 nslookup example.com |
看域名能否解析出 IP |
| 端口连通性 | telnet example.com 80 或 nc -vz example.com 80 |
看 TCP 层是否可达 |
| 完整 HTTP 交互 | curl -v http://example.com/path |
看请求和响应全过程 |
| 响应头 | curl -I http://example.com/path |
看状态码和 Header |
| 服务端日志 | tail -f /var/log/nginx/access.log |
看请求是否到达服务器 |
这个表不用背,关键是知道“边界在哪”。DNS 解决“域名有没有解释成 IP”的问题,telnet 解决“IP 端口通不通”的问题,curl 解决“HTTP 请求返回什么”的问题。404 是最后一类。
4.3 一条很土但好用的判断口诀
我总结了一条很土但很管用的口诀:看到 404 先查路径,看到 5xx 查服务,看到超时查链路,看到连接拒绝查端口。
这条口诀能省掉大量无用功。很多人拿到 404 就跑去 ping,Ping 通也不能证明 HTTP 路径正确,Ping 不通也不能解释为什么服务器还能回 404。把网络和 HTTP 分开看,问题会清楚很多。
5. 写在最后:一次 404 的坑
5.1 那次排查了两小时专线的经历
有次上线后,用户反馈某个接口返回 404,运维先查专线、查防火墙、查 DNS,折腾了快两个小时没有结论。我过去一对比,发现前端实际请求的地址比后端部署的地址多了个 /api/v2 前缀。改动配置后,接口立刻恢复。
那次之后我给自己立了一条规矩:遇到 404,先把自己的请求 URL 和服务端路由逐个字符对一遍。大多数 404 并不是“找不到服务器”,而是“服务器找到了,但路径对不上”。
5.2 我给自己的排查习惯
现在我排查 404 类问题,流程已经固定了。先看浏览器里的完整请求地址,再看服务端访问日志,最后用 curl 复现一次。如果 curl 能稳定复现 404,就直接去查路由、静态文件路径、部署配置,不再去碰网络。
这个习惯帮我避过很多坑。也给团队提过一个要求:所有服务必须提供健康检查接口,返回 200 才算存活。这样“网络通不通”和“服务资源存不存在”就能被分割成两个独立问题。看到 404,先别怀疑网络,把注意力放在服务器认为“什么东西不存在”上,往往才是最短的解决路径。
