Nginx在与PHP-FPM等FastCGI后端配合时,URL中脚本名之后附加的路径信息(也就是PATH_INFO)能否正确传给后端程序,完全取决于$fastcgi_path_info这个变量的取值。不少人在部署ThinkPHP这类需要PATH_INFO的框架时,会直接套用网上的配置,一旦URL格式稍有变化就出现404或者PATH_INFO为空的情况,根本原因就是没有理解这条变量背后的匹配与拆分逻辑。本文把$fastcgi_path_info的产生、传递以及常见踩坑点完整梳理一遍。

一、PATH_INFO到底是什么,为什么需要$fastcgi_path_info
先厘清概念。当一个请求URL形如/index.php/abc/def时,在FastCGI协议层面会被拆成两部分:真正的脚本文件是/index.php,而/abc/def就是PATH_INFO。PHP端可以通过$_SERVER['PATH_INFO']拿到这段值,很多MVC框架正是靠它来实现路由分发,比如ThinkPHP的index.php/module/controller/action模式。
问题在于,Nginx并不会自动帮你拆分URL。Nginx内部本身没有一个叫PATH_INFO的原生变量,它只知道请求的完整URI。要让PHP收到正确的PATH_INFO,必须在Nginx层把URI切开,把切出来的后半段赋给一个叫$fastcgi_path_info的变量,再通过fastcgi_param指令传给后端。换句话说,$fastcgi_path_info是Nginx提供的一个承载拆分结果的容器,它本身没有默认值,需要你自己通过匹配指令去填充。
如果不传这个参数会怎样?最典型的表现是框架路由失效,$_SERVER['PATH_INFO']不存在或者为空,程序拿不到模块和控制器名,最终抛出找不到路由的异常,或者直接返回404。所以理解它不是可选项,而是这类部署场景下的必经之路。
二、fastcgi_split_path_info指令的匹配与拆分原理
填充$fastcgi_path_info靠的是fastcgi_split_path_info指令。它接收一个正则表达式,要求至少包含两个捕获组,第一个捕获组对应脚本路径,第二个捕获组对应PATH_INFO部分。Nginx会用请求URI去匹配这个正则,匹配成功后,第一组捕获结果自动存入$fastcgi_script_name,第二组存入$fastcgi_path_info。
来看最经典的写法:
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass 127.0.0.1:9000;
}这段配置里,正则^(.+?\.php)(/.*)$是关键。以/index.php/admin/index/index为例,惰性匹配的.+?\.php会匹配到/index.php,剩余的/admin/index/index落入第二个捕获组。注意这里必须用惰性匹配,如果写成贪婪的(.+\.php),遇到/a.php/b.php这种URL时脚本名会被错误地识别成/a.php/b.php,导致文件查找失败。
location本身的写法也有讲究。[^/]\.php(/|$)表示匹配类似/xxx.php或/xxx.php/yyy的请求,这个正则同时覆盖了带PATH_INFO和不带PATH_INFO两种情况。如果location写成~ \.php$,那么以.php结尾的请求能进来,但/index.php/abc这种URL根本不会进入这个location,后面的拆分自然无从谈起,这是很多人配置后仍然404的首要原因。
还有一点容易被忽视:当请求URI中没有PATH_INFO部分(比如访问的就是/index.php本身)时,正则的第二个捕获组(/.*)匹配不到任何内容,此时$fastcgi_path_info为空字符串,传给PHP的PATH_INFO也就是空值,这是正常现象,不会影响脚本执行。
三、三个FastCGI参数的配合关系与完整示例
拆分完成后,真正传递给PHP的是几个fastcgi_param,它们各司其职。SCRIPT_FILENAME告诉PHP-FPM要执行哪个文件,通常由$document_root拼接$fastcgi_script_name得到;PATH_INFO就是拆出来的路径信息;而SCRIPT_NAME在设置了split指令后等于脚本路径部分。三者的对应关系可以用下面的例子直观理解。
| 请求URI | SCRIPT_FILENAME | PATH_INFO |
|---|---|---|
| /index.php | /var/www/html/index.php | (空) |
| /index.php/article/read | /var/www/html/index.php | /article/read |
| /api.php/v1/user | /var/www/html/api.php | /v1/user |
以ThinkPHP5为例,一份可以直接使用的server配置如下:
server {
listen 80;
server_name demo.ipipp.com;
root /var/www/thinkphp/public;
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last;
}
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}注意include fastcgi_params与手写param的顺序问题。如果fastcgi_params文件里本身已经有fastcgi_param SCRIPT_NAME $fastcgi_script_name之类的定义,而你在include之前又定义过同名参数,FastCGI会收到重复键值。虽然多数情况下PHP取最后一个,但建议把include放在前面,自己的显式定义放在后面,行为更可控。
四、常见问题排查与安全注意事项
第一个高频问题是PATH_INFO始终为空。排查顺序建议是:先确认请求确实进入了正确的location(可以临时把access_log格式里加上$uri和$fastcgi_path_info观察);再检查location正则是否覆盖了带路径后缀的形式;最后确认fastcgi_param PATH_INFO $fastcgi_path_info;这一行没有遗漏。三步走完基本都能定位。
第二个问题是路径穿透漏洞。历史上出现过经典的/logo.jpg/x.php攻击:由于Nginx把URI拆分后交给PHP,而PHP在查找文件时遇到不存在的路径会向前回溯,最终把图片当PHP执行,造成代码执行漏洞。修复方式一方面是升级Nginx与PHP版本,另一方面在配置层面加上try_files $fastcgi_script_name =404;,确保脚本文件真实存在才交给后端:
location ~ [^/]\.php(/|$) {
try_files $fastcgi_script_name =404;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
}第三个常见坑是cgi.fix_pathini相关的PHP配置。cgi.fix_pathinfo在php.ini中默认为1,表示PHP允许对SCRIPT_PATH做回溯猜测。如果出于安全把它改成0,同时又希望依赖Nginx传递的PATH_INFO,务必保证Nginx侧的拆分正则足够精确,否则可能出现原本正常的路由突然失效的情况。两者配合调试时,可以在脚本里打印$_SERVER确认SCRIPT_FILENAME、PATH_INFO、SCRIPT_NAME的实际值,比盲猜配置高效得多。
总结一下,$fastcgi_path_info的核心链路是:location正则放行带后缀的URI,fastcgi_split_path_info负责拆分并填充变量,fastcgi_param负责把变量交给PHP。理解了这条链路,无论是部署框架、排查404还是加固安全,都能做到心中有数。
Nginx配置fastcgi_path_infoPATH_INFO修改时间:2026-09-11 09:08:45