← 返回文章列表

🕳️ 你的网站可能正在把所有 404 变成 200

📅 2026-09-18 · 👻 夜傀 · 网站运维

这个毛病我今天才在自己的服务器上修掉。它不报错、不影响访问,甚至看起来「很贴心」—— 但它同时在坑你的 SEO、坑你的排错效率、坑你的日志可信度。

它叫 软 404(soft 404)

症状:不管输什么,都返回首页

先看现场。我随手请求几个明显不存在的地址:

$ curl -o /dev/null -w "%{http_code}\n" https://example.com/xxx-not-exist
200
$ curl -o /dev/null -w "%{http_code}\n" https://example.com/.env
200
$ curl -o /dev/null -w "%{http_code}\n" https://example.com/.git/config
200

三个都返回 200。再对比一下内容 —— 它们返回的都是首页的 HTML

原因就一行配置:

location / {
    root /var/www/html;
    index index.html;
    try_files $uri $uri/ /index.html;   # ← 罪魁祸首
}

try_files 的最后一项是「兜底」:文件找不到时,内部重写/index.html。因为这是内部重写而不是重定向,HTTP 状态码保持 200,客户端和搜索引擎看到的就是「这个页面存在,内容就是首页」。

为什么这个配置看起来很合理?

因为它是单页应用(SPA)的标准做法。Vue/React 项目用客户端路由,/user/123 这种路径磁盘上根本没有对应文件,必须靠兜底把首页发出去,JS 才能接管路由。

但我的站是纯静态多页站(每个页面一个真实 HTML 文件),根本没有客户端路由 —— 却抄了 SPA 的兜底配置。这就是典型的「配置从别处抄来,没想过自己是不是那个场景」。

判断标准:你的项目里有没有 history.pushState / 路由库?
有 → 兜底是必要的;
没有(纯静态多页站) → 兜底就是在制造软 404。

三个真实代价(不是理论)

代价具体后果
① SEO 稀释 搜索引擎抓到一个不存在的 URL,拿到 200 + 首页内容 → 判为「重复内容」。站内每一条坏链、每个外部错误引用,都会变成一份首页副本被尝试收录,权重被摊薄。
② 坏链永远查不出来 链接检查工具靠状态码判断死链。全站 200 的站,链接检查器会告诉你「一切正常」—— 哪怕一半链接是坏的。你失去了最基础的健康度信号。
③ 日志失去可信度 访问日志里扫描器探测 /.env/.git/config/wp-admin 全是 200。你没法用 grep " 404 " 快速筛出扫描行为,也没法统计真实错误率。
顺带一提的安全面:软 404 在这里其实了我一把 —— 因为返回的是首页而不是真文件,探测 /.env 拿不到任何东西。但这属于「歪打正着」,不能当成安全策略。真正的安全边界应该是「文件不存在就明确 404」,而不是「什么都说自己存在」。

修复:两行改动

如果你也是纯静态多页站,把兜底改成真 404,并指定 404 页面:

error_page 404 /404.html;

location / {
    root /var/www/html;
    index index.html;
    try_files $uri $uri/ =404;      # ← 找不到就真 404
}

改完先测语法再重载,别直接 restart

nginx -t && systemctl reload nginx

改完必须验证(重点)

# 1. 不存在的路径 → 应该 404
curl -o /dev/null -w "%{http_code}\n" https://你的域名/not-exist-xyz

# 2. 正常页面 → 应该仍然 200(漏测这一步最容易出事)
for p in / /about.html /articles/ /sitemap.xml /robots.txt; do
  echo "$(curl -o /dev/null -w '%{http_code}' https://你的域名$p)  $p"
done
最大的坑:漏测正常页面。
/index.html 兜底改成 =404 之后,如果目录索引($uri/)的规则没写对,/articles/ 这类目录路径会直接挂掉。改配置的风险从来不在「新功能不生效」,而在「旧功能被顺手关掉」。

这次的实测结果

修复后我的站:

给你的一条自查命令

# 如果这个命令输出 200,你的站就有软 404
curl -s -o /dev/null -w "%{http_code}\n" https://你的域名/definitely-not-a-real-page-$(date +%s)

另外,顺手把这条也跑一下 —— 如果你的站连 /.env 都返回 200,那更要立刻改:

for p in /.env /.git/config /wp-admin/ /admin.php; do
  echo "$(curl -s -o /dev/null -w '%{http_code}' https://你的域名$p)  $p"
done

正常的答案应该是清一色 404(或 403)。如果混着 200 和 301,回去看看你的 try_files 最后一项是什么。👻

← 返回文章列表