⭐ 推荐:社区规则条款 V1.0

Nginx 伪静态规则写错导致 404 怎么排查?

域名注册
域名注册 版主归队站长新人站长 👑年卡会员
发布于 2026-10-14 16:16 ·2 浏览 ·3 回复
内容摘要

排查 Nginx 伪静态规则错误导致的 404,可先用 curl -i 区分 Nginx 还是后端返回,再查 error/access 日志并记录 $uri 与 $request_uri,最后检查 location 匹配优先级,定位未执行或

照着这篇的步骤走一遍,你能在一次排查里确认 404 到底是 Nginx 返回的还是后端返回的,并定位到具体那一行伪静态规则。

第一步:先确认 404 是谁返回的

这一步要判断请求有没有落到 PHP/Java 后端,决定后面查 Nginx 还是查程序。用 curl 直接看响应头:

curl -i http://example.com/article/123.html

看两个地方:一是响应头里有没有后端框架的特征(比如 X-Powered-By: PHP/7.4、Set-Cookie: PHPSESSID=...),二是响应体。如果页面是 Nginx 自带的那个白底黑字 "404 Not Found / nginx/1.24.0",说明请求在 Nginx 层就被打回了,问题在 location / rewrite / try_files;如果页面是你自己框架的 404 模板(ThinkPHP 的 "页面错误"、Laravel 的 "404 Not Found" 页面),说明规则命中了、请求到了后端,问题在后端路由或 PATH_INFO 传参。

注意:别只看浏览器。浏览器会显示缓存页,用 curl -i 或 curl -I 看原始响应才准。

第二步:看日志确认到底匹配了哪条规则

这一步拿到请求实际进入的 URI 和 Nginx 给出的拒绝原因。开两个终端,一个跟错误日志:

tail -f /var/log/nginx/error.log

另一个跟访问日志:

tail -f /var/log/nginx/access.log

在 error.log 里,常见的两种行能直接定位问题:open() "/usr/share/nginx/html/article/123.html" failed (2: No such file or directory) 说明 try_files 或 root 指向的目录不对;rewrite or internal redirection cycle while processing "/index.php" 说明 rewrite 写成了死循环。

如果 access.log 看不出问题,在 http {} 块里的 log_format 加上 $uri 和 $request_uri:

log_format debug '$remote_addr "$request" uri=$uri args=$args status=$status';

请求里 /article/123.html 是 $request_uri,rewrite 之后的 $uri 才是实际用的,两者不一致恰好说明 rewrite 生效了。

第三步:检查 location 匹配顺序

这一步确认你写的规则有没有被更优先的 location 抢走。Nginx 的优先级是:= 精确 > ^~ 前缀 > ~、~* 正则(按出现顺序)> 普通前缀。最常见的情况是配置里同时存在:

location ^~ /static/ { }
location / { rewrite ^/(.*)$ /index.php?/$1 last; }

如果你把伪静态写在了 location / 里,但请求路径已经被 ^~ 的块接走,那就永远不会执行 rewrite。

用这条命令直接看 Nginx 最终合并后的完整配置(包含所有 include 进来的文件):

nginx -T | less

在输出里搜 server_name 找到你的 server 块,确认里面有几条 location、谁在前。

第四步:逐条核对 rewrite 和 try_files 写法

这一步把语法错误改成正确写法。两个高频坑:

坑一,正则缺 $ 或没加 flag。正确写法:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

try_files 的最后一个参数是「都不存在时内部转发到哪」,如果写成 /index.php,$query_string 就丢了,后端拿不到参数会返回 404。

坑二,PATHINFO 模式漏了 fastcgi 参数。ThinkPHP 之类的框架需要:

location ~ \.php(.*)$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

少写 fastcgi_split_path_info 和 PATH_INFO 这两行,/index.php/article/123 这类 URL 就必然 404。

注意:root 和 alias 不能混用。alias /data/www/; 后面必须带斜杠,且 alias 的路径是 location 匹配部分的替换,而 root 是拼接。写错这两个,文件明明存在也会报 No such file or directory。

第五步:改完必做的验证三步

这一步保证改动真的生效且不写坏线上。顺序固定:

nginx -t                    # 只检查语法,通过会输出 syntax is ok / test is successful
nginx -s reload             # 或 systemctl reload nginx
curl -i http://example.com/article/123.html   # 复测

注意:nginx -t 只验语法,不验逻辑。规则写得语法正确但正则不匹配,nginx -t 一样报 successful,必须靠 curl 复测。

如果 reload 后仍 404,用 nginx -T 再看一次,确认你改的文件确实被主配置 include 进去了,而不是改了个没被引用的备份文件。

小结

  • 先用 curl -i 判断 404 来自 Nginx 还是后端,再决定排查方向。
  • tail -f /var/log/nginx/error.log 里的 open() failed 和 rewrite or internal redirection cycle 是最有价值的两条线索。
  • location 优先级 = > ^~ > ~ > 前缀,规则被抢走是隐形 404 的常见原因。
  • try_files 末尾要带 $query_string,PATHINFO 模式必须配 fastcgi_split_path_info 和 PATH_INFO。
  • 改完固定执行 nginx -t → nginx -s reload → curl -i 复测,缺一步都可能白改。
Nginx 伪静态规则写错导致 404 怎么排查?,本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-1963.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 3

chinaz
chinaz 归队站长新人站长 1楼 2026-10-14 16:21

定位到具体哪一行,最快的办法不是看日志猜,而是把每条 rewrite 临时改成 return 200 "rule-X"; 做二分标记——直接看哪条先执行、哪条根本没进。

具体做法:把 location / 里那串 rewrite 逐条前面插一句 return 200 "hit-rule-1\n";(记得改完 nginx -t && nginx -s reload),用 curl 打请求,返回哪个标记就说明前面几条都没命中、卡在这一条。定位到行后把标记删掉,再单独调那一条。这比在日志里分辨 $uri 变化要直观得多,尤其是规则超过三条的时候。

接着要区分 rewrite ... last 和 break:last 会把重写后的 URI 重新丢回 location 匹配一轮,你帖子第三步说的"被 ^~ 抢走"往往就是这么来的;break 则在本 location 内继续,不再重新匹配。伪静态到 index.php 这种场景,try_files $uri $uri/ /index.php?$query_string; 通常比手写 rewrite 更稳,也不容易出循环。

还有个高频坑:你改的可能是 /etc/nginx/conf.d/xxx.conf,但实际生效的是 sites-enabled 里的软链或另一份 include 顺序更靠前的配置。先 nginx -T | grep -n "rewrite" 把最终合并后的配置打出来对一遍,能省掉一半的"改了没效果"。

lqyyzz
lqyyzz 观望站长 #1508 2楼 2026-10-14 16:40
chinaz:定位到具体哪一行,最快的办法不是看日志猜,而是**把每条 rewrite 临时改成 `return 200 "rule-X";` 做二分标记**——直接看哪条先…

思路对,插 return 做二分定位确实比在 access.log 里盯 $uri 变化快,但有两个前提得先说清,不然容易得出反向结论。

一是 return 属于 rewrite 阶段指令,一旦命中就直接短路返回,后面的规则和 location ~ \.php$ 的转发全都不会执行——所以它只能回答"顺序上卡在哪条",不能证明这条规则本身写错了。定位到行之后必须把标记删掉原样复现一次,别拿带标记的结果当结论。二是如果连 hit-rule-1 都没打出来,说明请求压根没进这个 location,这时候别再往下加规则,直接回头查 server 块匹配。

想少改配置文件的话,补一句 rewrite_log on; 并让 error_log 开到 notice 级别,Nginx 会把每条 rewrite 的输入输出配对打出来,等价于自动版标记,而且不改变执行逻辑。break 和 last 的区别你说得也对,last 会重新走一轮 location 匹配,被 ^~ 抢走基本就是这个原因。

try_files 那段同意,补一点:最后那一段 /index.php?$query_string 是内部重定向,同样要重走 location 匹配,所以 location ~ \.php$ 必须存在且 fastcgi_pass 正确,否则结果是 404 而不是你预期的报错,排查时容易误判成 root 写错。

nginx -T 那招我再加一刀:输出里每条片段前都有 # configuration file xxx 注释行,直接 grep 文件名比 grep rewrite 更快锁定来源。还有一种更阴的情况是请求被别的 server 块的 default_server 接走了,用 curl -H "Host: 你的域名" http://127.0.0.1/... 排除 DNS 和 CDN 干扰。

最后提醒一句:nginx -s reload 如果配置有语法错,会直接失败并继续用旧配置跑,你会以为"改了没生效",所以每次务必先 nginx -t。

满月居于空
满月居于空 版主资深会员新人站长 👑年卡会员 #1510 3楼 2026-10-14 16:49
lqyyzz:思路对,插 `return` 做二分定位确实比在 access.log 里盯 `$uri` 变化快,但有两个前提得先说清,不然容易得出反向结论。 一是 `re…

补的这两条前提都在点上,尤其 return,它是 rewrite 阶段直接短路返回,命中即结束,后面的规则和 location ~ \.php$ 压根不执行,所以只能证明顺序卡在哪,证不了规则本身写没写错,拿带标记的结果反推容易得出相反结论。

如果想做二分又不改变执行路径,有个更干净的办法:在各个 location 里塞一句 add_header X-Debug-Rule "1" always;,然后看响应头命中哪个块。关键在 always,不带它的话 add_header 默认只在 200/301/302 这类状态码下输出,404 恰好不生效,等于把最该排查的场景全漏掉了。它不打断 try_files 和内部重定向,比 return 更适合拿来定位。

rewrite_log on; 也有个边界值得说:它只打印 rewrite 模块的输入输出配对,try_files 的决策和内部重定向不打。所以 try_files 那段我还是习惯在 log_format 里加 $request_filename,它是 root/alias 拼接后的最终文件路径,和 $uri 一对照,是目录写错还是规则写错立刻分家。