今天下午 blog 某管理员踩到了 nginx try_files 的一个坑,导致 WordPress 博客采用了固定链接的页面无法访问。此问题持续半小时后修复。

原来的配置是这样的:


        location / {
            try_files $uri $uri/ /index.php;
            index  index.html index.htm index.php;
        }
        location ~ \.php$ { ... }

修改成了


        location / {
            try_files $uri $uri/ /index.php =404;
            index  index.html index.htm index.php;
        }
        location ~ \.php$ { ... }

增加的这个 =404,似乎是让 index.php 找不到的时候返回 404。但这么一改,形如 https://servers.blog.ustc.edu.cn/2014/09/ustc-telecom-link-failure/ 这样的链接返回的竟然是 index.php 的源码!这是怎么回事呢?

这要从 nginx try_files 的工作原理说起。以 try_files $uri $uri/ /index.php; 为例,当用户请求 http://servers.blog.ustc.edu.cn/example 时,这里的 $uri 就是 /example。try_files 会到硬盘里尝试找这个文件。如果存在名为 /$root/example(其中 $root 是 WordPress 的安装目录)的文件,就直接把这个文件的内容发送给用户。显然,目录中没有叫 example 的文件。然后就看 $uri/,增加了一个 /,也就是看有没有名为 /$root/example/ 的目录。又找不到,就会 fall back 到 try_files 的最后一个选项 /index.php,发起一个内部 “子请求”,也就是相当于 nginx 发起一个 HTTP 请求到 http://servers.blog.ustc.edu.cn/index.php。这个请求会被 location ~ \.php$ { ... } catch 住,也就是进入 FastCGI 的处理程序。而具体的 URI 及参数是在 REQUEST_URI 中传递给 FastCGI 和 WordPress 程序的,因此不受 URI 变化的影响。

配置修改之后,/index.php 就不在 fall back 的位置了,也就是说 try_files 在文件系统里找到 index.php 之后,就会直接把文件内容(也就是 PHP 源码)返回给用户。这里的坑就是 try_files 的最后一个位置(fall back)是特殊的,它会发出一个内部 “子请求” 而非直接在文件系统里查找这个文件。