照着这篇的步骤走一遍,你能在一次排查里确认 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 复测,缺一步都可能白改。