对于很多人来说,配置Nginx+PHP无非就是搜个教程然后复制粘贴而已。听起来好像没什么问题,但事实上,网上很多资料年久失修,漏洞百出。如果只是复制粘贴而不要求更深入的理解,迟早要付出代价。

假设我们用PHP实现一个前端控制器,或者说白了就是一个统一的入口:将所有的PHP请求发送到同一个文件,然后通过解析这个文件中的“REQUEST_URI”来实现路由。

这个时候很多教程都会教你这样配置Nginx+PHP:

服务器{
    听80;
    服务器名称 www.sxzhongrui.com;

    根/路径;

    地点/{
        索引index.htmlindex.htmindex.php;

        if (!-e $request_filename) {
            最后重写./index.php;
        }
    }

    位置 ~ \.php$ {
        包括 fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /路径$fastcgi_script_name;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index索引.php;
    }
}

这里面有很多错误,或者至少是难闻的气味。看一下,你可以找到一些。

我们首先需要了解Nginx配置文件中指令的继承关系:Nginx配置文件分为很多块。常见的从外到内有“http”、“server”、“location”等,默认的继承关系是从外到内,也就是说内层块会自动获取外层块的值默认值(也有例外,详细信息请参阅参考资料)。

参考:理解NGINX配置继承模型

让我们从“index”指令开始,它在问题配置的“location”中定义:

位置 / {
    索引index.htmlindex.htmindex.php;
}

以后一旦需要添加新的“位置”,必然会出现重复定义“索引”的指令。这是因为多个“位置”是横向关系,没有继承关系。这时,应该在“服务器”这里定义“索引”,借助继承关系,“索引”命令可以在所有“位置”生效。

参考:Nginx 陷阱

让我们看一下“if”命令。不夸张地说,它是最容易被误解的Nginx命令:

if (!-e $request_filename) {
    最后重写./index.php;
}

很多人喜欢用“if”指令来做一系列的检查,但这其实是“try_files”指令的职责:

try_files $uri $uri/ /index.php;

另外,初学者常常认为“if”指令是内核级指令,但实际上它是rewrite模块的一部分,加上Nginx配置它实际上是声明性的而不是过程性的,因此当它与非重写模块的指令混合时,结果可能不是您想要的。

参考:IfIsEvil 以及 nginx “location if” 的工作原理

看下面的“fastcgi_params”配置文件:

include fastcgi_params;

Nginx 有两个 fastcgi 配置文件,分别是“fastcgi_params”和“fastcgi.conf”。它们之间没有太大区别。唯一的区别是后者比前者多了一行“SCRIPT_FILENAME”。定义:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

注意:$document_root 和 $fastcgi_script_name 之间没有 /。

最初,Nginx 只有“fastcgi_params”。后来发现很多人在定义“SCRIPT_FILENAME”时使用了硬编码,所以引入了“fastcgi.conf”来规范使用。

但这又引出了一个问题:为什么必须引入新的配置文件而不是修改旧的配置文件呢?这是因为“fastcgi_param”指令是数组类型。与普通指令相同:内层代替外层。与普通指令不同的是,在同一级别多次使用时,是添加而不是替换。换句话说,如果“SCRIPT_FILENAME”在同一级别定义了两次,它们都会被发送到后端,这可能会导致一些潜在的问题。为了避免这种情况,引入了新的配置文件。

参考:FASTCGI_PARAMS 与 FASTCGI.CONF – NGINX 配置历史记录

另外,我们还需要考虑一个安全问题:当PHP开启“cgi.fix_pathinfo”时,PHP可能会将错误的文件类型解析为PHP文件。如果Nginx和PHP安装在同一台服务器上,最简单的解决方案是使用“try_files”命令进行过滤:

try_files $uri =404;

参考:Nginx文件类型错误解析漏洞

基于之前的分析,这里有一个改进版本。是不是比原来的版本干净很多?

服务器{
    听80;
    服务器名称 www.sxzhongrui.com;

    根/路径;
    索引index.htmlindex.htmindex.php;

    地点/{
        try_files $uri $uri //index.php;
    }位置 ~ \.php$ {
        try_files $uri =404;

        包含 fastcgi.conf;
        fastcgi_pass 127.0.0.1:9000;
    }
}

其实还是有一些缺陷的,主要是“try_files”和“fastcgi_split_path_info”兼容性不够。虽然可以解决,但是解决方案比较丑陋,就不详细说了。有兴趣的可以参考问题描述。

补充:因为“location”已经被限制了,所以“fastcgi_index”其实没有必要。

希望大家以后不要复制粘贴。如果实在无法更改,请复制粘贴这篇文章。

原文:http://www.sxzhongrui.com/2013/10/23/290