如何修复 404 Not Found 错误
404 Not Found 是 Web 服务器在收到一个有效请求、但该请求所指向的资源在服务器上不存在时返回的 HTTP 状态码。与 500 或 503 不同,服务器本身并没有出问题 —— 请求已被理解,只是没有匹配的文件、路由或处理器可供返回。浏览器随后显示那个熟悉的“404 —— 页面未找到”界面,搜索引擎也会把该 URL 视为不存在。
404 并不总是一场危机。资源随时在被删除、改版、重组,少量 404 是运营站点的正常代价。问题出现在这些 404 指向的是本应存在的内容时 —— 一个在线的商品页、一篇已发布的文章、你的应用依赖的某个 API 端点。这类 404 会流失访客、打断集成、侵蚀搜索排名。好消息是,几乎每个 404 都可以追溯到六个根本原因之一,而每一个都有明确的修复方法。本指南逐一讲解,并附上 Nginx 和 Apache 的服务器配置示例。
什么是 404 Not Found?
当浏览器向服务器请求 https://example.com/about.html 时,服务器把该路径映射到磁盘上的一个文件(或交给应用路由处理)。如果文件缺失、路由没有匹配项,且没有改写规则挽救该请求,服务器就会返回 HTTP/1.1 404 Not Found。响应体通常是一个通用错误页,但真正起作用的是响应头里的状态码 —— 它向每个客户端和爬虫明确表示资源不存在。
需要区分真实的 404(服务器确实找不到资源)与软 404 —— 即应用返回 200 OK 状态码、却在页面正文中显示“页面未找到”。搜索引擎能识别软 404 并按真实 404 处理,但它们更难被发现,因为状态码在“撒谎”。排查 404 时,务必用 curl -I 确认实际状态码,而不要只看屏幕上的提示。
404 与 403 Forbidden 也不同。403 表示资源存在,但服务器拒绝让你查看,通常是因为权限或访问规则。404 则表示服务器连资源是否存在都不予确认。把两者搞混,会导致你白白花时间去修改一个根本不存在的文件的权限。
常见原因
在阅读完整指南之前,先用下表把你的情况匹配到最可能的根本原因和对应的解决步骤。
| 原因 | 发生位置 | 修复方法 |
|---|---|---|
| URL 拼写错误 —— 大小写、末尾斜杠或百分号编码不对 | 地址栏、外链、站点地图 | 与真实路径逐字符对比(步骤 1) |
| 文件缺失,或因权限/属主而无法读取 | 服务器文件系统(/var/www、public_html) |
目录设为 755、文件设为 644;修正属主(步骤 2) |
损坏的 try_files 或 RewriteRule 把真实路径送到不存在的兜底 |
nginx.conf、Apache .htaccess |
禁用规则并二分定位出错行(步骤 3) |
| 页面在改版中被移动或重命名 | 旧书签、搜索索引、内部链接 | 在访问日志中找到新位置(步骤 4) |
| 浏览器或 CDN 返回缓存的 404 或过期 URL | 客户端浏览器、Cloudflare/CDN 边缘缓存 | 强制刷新、清除 CDN、无痕测试(步骤 5) |
| 永久迁移的 URL 没有配置重定向 | 服务器配置、CMS 重定向规则 | 添加 301 到新 URL,或返回 410 Gone(步骤 6) |
分步修复指南
步骤 1:检查 URL 是否有拼写错误
最容易修复的 404 是拼写错误造成的。逐字符阅读请求的 URL,并与服务器上文件的真实路径对比。要留意三件浏览器会悄悄规范化、但服务器不会处理的事:末尾斜杠(/about 与 /about/)、字母大小写(在 Linux 等区分大小写的文件系统上 About.html 与 about.html)、以及百分号编码(%20 与字面空格)。用修正后的 URL 验证拼写理论:
# 查看服务器返回的确切状态码
curl -I https://example.com/about.html
如果修正后的 URL 返回 HTTP/1.1 200 OK,那么原来的 404 就是拼写错误或畸形链接。更新产生错误 URL 的外链,避免其他访客撞上同样的死胡同。只有当修正后的路径也返回 404 时,才需要继续排查服务器。
步骤 2:检查文件权限
如果 URL 没问题,确认文件确实存在于磁盘上、且 Web 服务器能读取它。SSH 登录服务器并列出目录:
ls -l /var/www/example/about.html
# 期望结果:-rw-r--r-- 1 www-data www-data ... about.html
Web 服务器进程(通常是 www-data、nginx 或 apache)需要对文件有读权限,并对每一个父目录有执行(穿越)权限。一个标准且安全的设置是目录 755、文件 644,属主与 Web 服务器用户一致。如果文件存在但权限或属主阻止了服务器访问,就会得到 404(有时是 403)。修复方法:
# 修正属主和权限
chown -R www-data:www-data /var/www/example
find /var/www/example -type d -exec chmod 755 {} \;
find /var/www/example -type f -exec chmod 644 {} \;
由其他用户上传、从 tar 包恢复、或由 CI 流水线同步过来的文件,常常带着错误的属主到达 —— 这是部署后突然出现 404 最常被忽略的原因之一。
步骤 3:验证 .htaccess 或 Nginx 改写规则
现代站点很少直接从磁盘提供文件,而是由改写规则把请求路由到应用或兜底。如果这条规则配置错误,一个完全有效的 URL 会被改写到一个不存在的路径,服务器于是返回 404 —— 哪怕文件就摆在那里。典型例子是单页应用的 try_files 落到一个缺失的 index.html,或是 WordPress 的 .htaccess 固定链接规则被清空了。
要隔离问题,临时禁用改写层并重载。在 Apache 上把 .htaccess 重命名为 .htaccess.bak;在 Nginx 上注释掉 try_files 行并执行 nginx -s reload。如果页面现在能加载,改写规则就是元凶。恢复文件并二分排查 —— 逐块重新启用规则,每次重载,直到 404 复现。最后启用的那块就包含出错的指令。
步骤 4:检查被移动或重命名的文件
改版、CMS 迁移和重组经常重命名或迁移页面。新站点运行完美,但每个旧书签、搜索结果和内部链接仍指向原 URL —— 而它现在返回 404。这些不是拼写错误,文件也没有缺失,它们只是搬到了别处。
在访问日志中找出 404 路径,并把每一条匹配到新位置:
# 列出最近 24 小时内最频繁的 404
grep ' 404 ' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head
对每条高频 404 路径,在新站点上找到替换页面并添加 301 重定向(见步骤 6)。不要让迁移后的内容继续返回 404 —— 哪怕只有几条高流量的死链,也可能拖累抓取预算和搜索排名。
步骤 5:清除浏览器和 CDN 缓存
浏览器和 CDN 会缓存响应,其中也包括 404。服务器上可能几分钟前就已经修好了,但你看到的仍是旧的“未找到”界面,因为 404 响应被缓存在边缘节点或你的浏览器里。在花一小时调试一个早已解决的问题之前,先排除缓存因素。
强制刷新页面(Ctrl+Shift+R 或 Cmd+Shift+R),然后在一个全新的无痕窗口中测试。如果你使用了 Cloudflare 之类的 CDN,从控制台或通过 API 清除缓存。如果清除后页面能加载,说明 404 只是过期的缓存条目,而非服务器实时响应 —— 源站无需再作任何改动。
步骤 6:设置正确的 301 重定向
一旦确认某个 URL 已永久迁移,就不要让它继续返回 404。从旧 URL 到新 URL 添加 301 Moved Permanently 重定向。301 会告诉浏览器和搜索引擎更新记录,把链接权重传递到新页面,对 SEO 远比留下死胡同要好。对于确实永久删除、没有替换的内容,改返回 410 Gone —— 它告诉爬虫删除是有意的,可以更快地把该 URL 从索引中移除。
对于你无法消除的少量 404(没有替换的已删页面、你无法控制的错误外链),提供一个带站点导航和搜索框的自定义 404 页面,让访客能找到出路,而不是直接跳出。
服务器配置示例
Nginx 和 Apache 都各自提供一条指令来优雅地处理缺失文件,以及一条指令来添加 301 重定向。把这些当作起点,按你自己的目录结构调整路径。
Nginx:try_files 与自定义 404
try_files 指令告诉 Nginx 在磁盘上找不到文件时该怎么办。最后一个参数是兜底 —— 通常是你的前端控制器或单页应用外壳。配合 error_page 为真正未匹配的路由提供一个带品牌标识的 404 页面。
server {
listen 80;
server_name example.com;
root /var/www/example;
location / {
# 先找精确文件,再找目录,最后落到 SPA 外壳
try_files $uri $uri/ /index.html?$args;
}
# 为未匹配的路由提供带品牌标识的 404 页面
error_page 404 /404.html;
location = /404.html {
internal;
root /var/www/example;
}
# 把旧路径 301 重定向到新位置
location = /old-page.html {
return 301 /new-page.html;
}
}
Apache:.htaccess 改写与重定向
在 Apache 上,同样的行为写在 .htaccess 里。ErrorDocument 控制 404 页面,RewriteRule 把缺失文件送到前端控制器,Redirect 处理永久迁移。
# 为缺失路由提供带品牌标识的 404 页面
ErrorDocument 404 /404.html
<IfModule mod_rewrite.c>
RewriteEngine On
# 如果请求不是真实文件或目录,路由到前端控制器
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
# 把旧路径 301 重定向到新位置
Redirect 301 /old-page.html /new-page.html
编辑 Nginx 后,用 nginx -t && nginx -s reload 校验并重载。Apache 会立即读取 .htaccess 的改动,无需重启。
快速参考表
4xx 系列状态码都描述客户端问题,但含义大不相同。把它们搞混会导致修错对象 —— 给一个不存在的文件改权限,或去寻找一个其实被禁止访问的缺失页面。在动手调试之前,先用下表正确读懂状态码。
| 状态码 | 名称 | 含义 | 典型原因 |
|---|---|---|---|
| 400 | Bad Request | 服务器无法解析该请求 | 畸形的 URL 或查询字符串 |
| 401 | Unauthorized | 需要认证但缺失或无效 | 缺失或过期的登录凭据 |
| 403 | Forbidden | 已认证(或未认证)但不被允许 | 文件权限、.htaccess 拒绝规则 |
| 404 | Not Found | 资源在服务器上不存在 | 断链、缺失文件、错误的改写规则 |
| 410 | Gone | 资源曾存在但已被永久删除 | 有意删除且无替换的页面 |
实用地区分一下:401 问“你是谁?”,403 说“我知道你是谁,答案是不行”,404 说“我根本不知道你在要什么”,410 说“那东西曾经存在,但我已经故意删了”。当页面被刻意删除时,选择 410 而非 404 —— 它能加快从搜索索引中移除。
常见问题
404 错误对 SEO 有害吗?
少量 404 是正常的,本身无害 —— Google 只是把那些 URL 从索引中移除。真正有害的是那些指向本应存在、且有外链或搜索流量的页面的 404。它们会流失链接权重和排名。用 301 重定向修复重要页面上的 404,让真正删除的页面返回 410 Gone 或一个有帮助的自定义 404,避免访客被困住。
如何找出站点上所有的 404 错误?
三个来源覆盖大多数情况。Google Search Console 在 覆盖率 → 已排除 → “未找到 (404)” 下列出 404。服务器访问日志实时显示每个 404 —— 用 grep ' 404 ' access.log 过滤。而服务器端错误监控或像 Screaming Frog 这样的链接爬虫,在全站抓取时会发现损坏的内部链接。三者交叉对比,按流量排定优先级。
我应该把 404 重定向到首页吗?
通常不建议。把每个 404 批量重定向到首页会被 Google 视为软 404 —— 首页返回 200,但由于内容不相关,原 URL 实际上仍会被丢弃。正确做法是把每个旧 URL 单独 301 到最接近的主题替换页。只有当不存在相关替换页时,才把访客送到首页(或带导航的自定义 404)。
软 404 和硬 404 有什么区别?
硬 404 在 HTTP 头里返回真实的 404 Not Found 状态码。软 404 返回 200 OK,却在正文中显示“页面未找到” —— 通常是因为应用捕获了缺失记录并渲染了错误模板,却没有设置状态码。搜索引擎能识别软 404 并按真实 404 处理,因此务必用 curl -I 确认状态码,并确保应用在内容缺失时返回真实的 404 状态。
总结
404 Not Found 是服务器诚实地说“那个资源不在这里”。大多数情况下修复很简单:确认 URL 拼写正确、验证文件存在且权限正确、确保改写规则没有把有效路径送错地方、找到已迁移的内容、清除缓存的 404、并为任何永久迁移的地址添加 301 重定向。按顺序走完这六个步骤,每个阶段都用 curl -I 确认真实状态码。记住,完成信号是规范 URL 返回一个干净的 200 —— 而不是错误页面消失了。