nginx 把 SCRIPT_NAME 传成了带路径的整串 URI,主题拿它推算「当前域名」,算出来是个不存在的地址,于是判定未授权,把内页全部降级成首页。问题不在 Typecho,不在 PHP,也不在主题授权。

环境:Handsome 9.x版本(八百年过去了,作者的10.1版本还是开发测试版)

一、现象

点开自己博客里的任意一篇文章,地址栏变了,页面不动,还停在首页。刷新一下首页正常,后台能登进去,但是文章打不开。

我拿 curl 把几个地址跑了一遍:

请求状态码返回大小实际内容
/20057450首页 ✓
/index.php/archives/26/20057450首页 ✗
/index.php/feed/20057450首页,不是 RSS ✗
/index.php/zzzznotexist/20057450首页,本该 404 ✗
/index.php/action/login302—正常跳转 ✓

四个完全不同的地址,返回同一份 57450 字节。文章页、RSS、甚至连不存在的路径,统统是首页。

现象:/action/login 是唯一正常的。它不渲染模板,说明 Typecho 核心、数据库、路由都没事,坏的只是走主题渲染的那一层。


二、原因

后来翻主题代码才看明白,它是用 dirname($_SERVER['SCRIPT_NAME']) 推算当前域名的:

  • 正常:SCRIPT_NAME = /index.php,dirname 得 /,域名 = https://你的域名 ✓
  • 故障:SCRIPT_NAME = /index.php/archives/26/,dirname 得 /index.php/archives,域名 = https://你的域名/index.php/archives ✗

一个不存在的域名,授权自然对不上,主题就静默返回首页。
(大肥鱼说的,正常来说授权对不上应该返回主题作者的授权页面,为什么静默返回主页我不太清楚,我也不想去破解作者的主题找原因)

那 SCRIPT_NAME 为什么会带上一串路径?问题出在宝塔的 PHP 转发配置里,include 放错了位置,nginx/conf/php/82.conf:

location ~ \.php(.*)$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;

    set $real_script_name $fastcgi_script_name;
    if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
        set $real_script_name $1;
        set $path_info $2;
    }

    fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
    fastcgi_param SCRIPT_NAME     $real_script_name;
    fastcgi_param PATH_INFO       $path_info;

    include fastcgi_params;      # ← 就是这一行
}

原本的思路是:$1 取脚本路径,$2 取 pathinfo,各自赋给变量再传下去。但 fastcgi_params 这个文件里自带一句:

fastcgi_param SCRIPT_NAME $fastcgi_script_name;

nginx 的 fastcgi_param 是后写的覆盖先写的,不是先到先得。include 在最下面,等于用原始的整串 URI 把前面辛苦算出来的值又盖了回去。

这里看起来完全正确。但set 那一句改一百遍都不会有效果,问题只是 include 的位置。

另外那行 try_files $uri =404; 也有问题:/index.php/archives/26/ 在磁盘上找不到对应文件,pathinfo 修好之后它会立刻把所有内页打成 404,一起改掉。


三、改法

最后换成了 nginx 的官方指令,把那段手写的 if 拆解整个删掉:

location ~ \.php(.*)$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;

    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    try_files $fastcgi_script_name =404;

    include fastcgi_params;

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param SCRIPT_NAME     $fastcgi_script_name;
    fastcgi_param PATH_INFO       $fastcgi_path_info;
    fastcgi_param REQUEST_URI     $request_uri;
}

这么写有三个好处:

  1. include 的顺序不再致命。fastcgi_split_path_info 会把 $fastcgi_script_name 就地修正成脚本路径,于是 fastcgi_params 里那句 SCRIPT_NAME 自动就是对的,哪怕有人把 include 又挪回最后也不会翻车。
  2. try_files 可以放心用。此时它校验的是磁盘上真实存在的脚本,不会误杀 pathinfo。
  3. 少一个 if。nginx官方wiki有一页就叫If Is Evil,这是作者自己承认的历史包袱。

四、验证

任意点开一个博客,正常显示内容,问题解决。

最后修改:2026 年 11 月 02 日