配置 nginx + PHP 的文章网上一搜一大把,很多教程通常都是把很大的篇幅给了如何下载源代码,如何编译等内容,对配置文件的编写一笔带过,一般就是给一个现成的配置文件了事。由于Linux发行版众多,而且时间跨度非常大,所以网上搜到的配置文件有不少都存在过时甚至错误的内容,有的放到现在的环境下根本无法运行,有的则会留下一些隐患。
其实,对于绝大多数普通用户来说,使用包管理工具来安装nginx + PHP已经可以满足需求,不必自己下载源代码包进行编译,但是对于配置文件的编写却会有一些五花八门的需求。因此,学习nginx的配置文件编写才是整个过程中的重中之重,直接复制粘贴教程里的现成配置代码通常后患无穷。
笔者这里就简单说说该如何去编写一份正确的 nginx + PHP 配置文件。
我们就以一份我所使用的配置文件作为例子进行讲解。配置见下:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
access_log /var/log/nginx/example.com_access.log;
error_log /var/log/nginx/example.com_error.log;
root /var/www/html/example;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ /index.php?q=$uri&$args;
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_param PATH_INFO $path_info;
include fastcgi.conf;
}
location ~ /\.ht {
deny all;
}
}
配置文件的结构
首先我们看一下配置文件的结构,配置文件是由server块和若干个location块构成的,所有的location块都包含在server块中。一个server块用来定义一个虚拟服务器,一个location块用来表示对一类URI的处理动作。这里不理解location块的意义不要紧,我们下面还会详细讲。nginx的变量继承关系是从外到内的,也就是说在server块中定义的量,在该server块里面的location块中同样有效。因此我们要在server块中定义各location块中共同的部分,比如根目录,index文件等信息。
公共信息部分
我们先看第一段,这一段是虚拟服务器的基本信息:
listen 80;
listen [::]:80;
server_name example.com www.example.com;
这一段中,listen定义的是监听端口和IP,如果不指定IP的话,默认监听0.0.0.0,也就是说本机所有IP都可以访问,当然也可以指定具体的IP。“[::]”表示的IPv6的“0.0.0.0”,也就是说,该虚拟服务器同时在IPv6上监听,且监听本机所有IPv6地址。
server_name定义的是该虚拟服务器绑定的域名,当有多个虚拟服务器都监听80端口时,需要通过域名来具体区分究竟是访问哪个虚拟服务器。nginx可以为一个虚拟服务器绑定多个域名,不同域名用空格分开。
再看接下来的日志部分:
access_log /var/log/nginx/example.com_access.log;
error_log /var/log/nginx/example.com_error.log;
这里定义了访问日志和错误日志的位置。一般情况下,各Linux发行版中的nginx附带的主配置文件nginx.conf中已经定义了访问日志和错误日志的地址,但是这个日志会把所有的虚拟服务器的日志混在一起。如果虚拟服务器比较多的话,还是推荐分开定义日志位置,这样每个站点的日志有独立的文件,方便查看。
如果不需要日志的话,可以用下面的方式关掉日志:
access_log off;
error_log off;
之后便是一些和文档相关的定义:
root /var/www/html/example;
index index.html index.htm index.php;
root定义的是虚拟服务器的根目录,index定义的是在访问一个目录地址(即URI结尾为“/”)时默认访问的文件。可以通过index指定多个文件作为默认文件,nginx在处理请求时会按顺序寻找默认文件。比如在这个例子当中,当我们访问http://example.com/时,nginx会先寻找有没有index.html,如果没有的话会继续向后寻找。注意:index指令会引起“内部跳转”。不知道什么是“内部跳转”也无所谓,下面会讲到。
重点——location块
剩下的部分就都是location块了,我们可以发现每个location块都是下面这种形式:
location [ = | ~ | ~* | ^~ ] uri { ... }
其中,“[ = | ~ | ~* | ^~ ] uri”表示URI的匹配规则,大括号里的内容表示对匹配到的URI的处理方式。就像上文所说的那样——“一个location块用来表示对一类URI的处理动作”。location块的匹配规则中带有“~”修饰符的都是以正则表达式表示的匹配关系。除“~”修饰符外,还有“=、~*、^~”修饰符,都有不同的含义,具体见下表:
“=”表示精确匹配,只有完全匹配才有效。
“~”表示通过正则表达式匹配,对大小写敏感。
“~*”表示通过过正则表达式匹配,对大小写不敏感。
“^~”表示通过前缀进行匹配,并且优先级在正则表达式之前。
当nginx处理URI时,带有这四个修饰符的规则会被优先匹配,如果匹配不到,会对无修饰符的规则进行匹配。无修饰符的规则则是按照URI的前缀进行匹配,例如“/”就是匹配所有http://example.com/下面的请求,例如/index.htm或者/foo/index.htm。
所有的匹配,包括有修饰符和无修饰符的匹配规则,都是按照最大匹配原则进行匹配,也就是说,假设同时存在“/”和“/foo/”两个规则的话,“/foo/index.htm”会匹配到后者上。
location的URI匹配问题是一个比较复杂的问题,更多的说明和例子见这里:http://nginx.org/en/docs/http/ngx_http_core_module.html#location
我们先来看第一个location块:
location / {
try_files $uri $uri/ /index.php?q=$uri&$args;
}
匹配规则是“/”,代表匹配http://example.com/下面的所有请求,且是长度最短的常规前缀匹配,因此综合nginx自身的修饰符优先级规则和最大匹配优先规则,该规则的优先级最低。
规则内部只有try_files … 一条语句,try_files用来顺序判断所列文件是否存在,并返回第一个存在的文件。try_files的前几个参数都是用来尝试寻找的文件,而最后一个参数则是无法寻找到前面那些文件而返回的结果。例如,将最后一个参数指定为“=404”则会返回HTTP 404错误代码。需要特别说明的是,try_files的参数中,除最后一个参数外,都只作用在当前块中,均不会产生“内部跳转”。
大家应该会想起来前面也提到过“内部跳转”的事,这里就简单解释一下这个内部跳转。简单来说,内部跳转就是一个会触发location URI匹配的跳转,但是却不是通过返回HTTP 301/302给客户端来实现的。
我们来看看能触发内部跳转和不能触发内部跳转的区别:
假设我现在在用WordPress,permalink都是“http://example.com/nginx-php-configuration-tutorial/”这种形式的,我需要将URI重定向至/index.php?q=$uri&$args来处理这种permalink。
如果我的try_files写成下面这样:
try_files $uri $uri/ /index.php?q=$uri&$args =404;
大家会发现返回的永远是HTTP 404,因为try_files只有最后一个参数可以产生内部跳转,其他的会被当作要寻找的文件,所以会在当前位置寻找文件“/index.php?q=$uri&$args”,而这个文件显然是不存在,自然就会返回HTTP 404。
如果我们把最后的“=404”删掉,“/index.php?q=$uri&$args”成为了最后一个参数,就会发生内部跳转,从而触发处理php文件的location块,此时我们的permalink就会被正常处理了。
关于try_files,更多细节请见这里:http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files
刚才说了很多关于try_files的事情,我们再来看第二个location块:
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_param PATH_INFO $path_info;
include fastcgi.conf;
}
这个块的作用就是处理php文件。我们先从匹配规则来看,规则以“~”开头,表明它是一条以正则表达表示的匹配规则。“[^/]\.php(/|$)”是一条正则表达式,表示的是匹配所有结尾为“.php”和“.php/”的URI,例如“http://example.com/index.php”和http://example.com/index.php/foo/bar/”,后面这种我在用ThinkPHP的时候曾经用过,所以我个人认为对这种URI的兼容也是有必要的。
你在其他教程里,会看到另一种简单的匹配规则写法“\.php$”,这种不能匹配到我上面所说的第二种URI,如果对第二种URI无需求的话,也可以使用这种匹配方式。
块中的第一行,fastcgi_split_path_info用来定义一个正则表达式,该正则表达式可以将URI分离成$fastcgi_path_info和$fastcgi_script_name,前者是脚本所在的目录,后者是脚本文件名,这些都是fastcgi必须的参数。第二行的“set …”则用来将$fastcgi_path_info存储在$path_info中,因为接下来的try_files会清空$fastcgi_path_info。如果你的匹配规则使用的是简单的形式的话,那么这里的正则表达式也可以写成“^(.+\.php)(/.+)$”,两个都可以。
关于为什么try_files会清空$fastcgi_path_info,请看这里:https://trac.nginx.org/nginx/ticket/321
接下来的try_files作用和前面一样,不再赘述。
“fastcgi_pass”是将php脚步交给php-fpm处理,这里用的是UNIX socket,如果用的是TCP port的话,需要写成下面的形式:
fastcgi_pass 127.0.0.1:9000;
究竟是写成哪种形式,和PHP-FPM的配置有关,可以查看PHP-FPM的www.conf来判断,其中定义的listen就是监听的TCP port或者UNIX socket。
如果太懒的话,还有一种更简单粗暴的方式,在PHP-FPM已经启动的情况下,直接用netstat,如果发现有程序在监听127.0.0.1:9000的话,那就可以用TCP port方式,如果有程序在监听php7.0-fpm.sock的话,就是UNIX socket方式。
“fastcgi_param”用来设置fastcgi的参数,这里设定的是PATH_INFO,用的就是刚才的$path_info。
“include fastcgi.conf”是加载fastcgi.conf,该文件中保存着更多的fastcgi参数。在有些教程里,这里是这么写的:
include fastcgi_params;
还有的教程是这么写的:
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/foo$fastcgi_script_name;
除上面两种,还有的教程是这么写的:
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
其实上面的写法都对。如果大家查看fastcgi_params会发现它的内容和fastcgi.conf的内容几乎一样,在有的发行版中,它和fastcgi.conf完全一样,在其他发行版中,fastcgi_params会比fastcgi.conf少一条SCRIPT_FILENAME。
所以上面的写法其实就是不同时代,不同发行版的写法。早年的fastcgi_params中没有定义SCRIPT_FILENAME,所以就有人手写一条“fastcgi_param SCRIPT_FILENAME /var/www/foo$fastcgi_script_name;”,但是这种写法实际上是“硬编码”,一旦环境变动就要进行修改,于是大家统一成“fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;”,这样适应性就强了很多。为了方便大家,后来nginx中直接添加了fastcgi.conf,其实就是fastcgi_params加上后面那条定义SCRIPT_FILENAME的语句,为了兼容性,原来的fastcgi_params依然保留了下来。
但是有些发行版,例如Ubuntu 16.04,将fastcgi_params改成和fastcgi.conf一模一样的了,这样即使用“include fastcgi_params;”也是没问题的。
下面说说最后一个location块:
location ~ /\.ht {
deny all;
}
这个块的作用就是禁止对一切.htaccess文件的访问,目的就是为了提升在nginx和apache共享目录时的安全性,避免通过nginx读取到apache需要用的.htaccess。如果你并不使用apache的话,这个块也就没有存在的意义。
测试
在test.php文件中写入下面的代码:
<?php var_export($_SERVER)?>
将该文件放置于根目录下,通过浏览器访问下面的地址:
/test.php
/test.php/
/test.php/foo
/test.php/foo/
/test.php/foo/bar.php
/test.php/foo/bar.php?v=1
如果访问时不出现HTTP 404,且返回的各参数都符合预期,证明nginx + php配置正确。
本作品使用基于以下许可授权:Creative Commons Attribution-NonCommercial 4.0 International License.
那些小线条是越集越多喵