Nginx + phpFPM: PATH_INFO always empty - nginx

I configured nginx stable (1.4.4) + PHP (using FastCGI, php-fpm) on Debian. That works fine:
location ~* ^/~(.+?)(/.*\.php)$ {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
alias /home/$1/public_html$2;
fastcgi_pass unix:/var/run/php5-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_index index.php;
autoindex on;
}
I use the PATH_INFO variable, therefore I added the following line to fastcgi_params:
fastcgi_param PATH_INFO $fastcgi_path_info;
And in /etc/php5/fpm/php.ini:
cgi.fix_pathinfo = 0
I think that should work, but when I print out all server variables, PATH_INFO is always empty:
array (
'USER' => 'www-data',
'HOME' => '/var/www',
'FCGI_ROLE' => 'RESPONDER',
'QUERY_STRING' => '',
'REQUEST_METHOD' => 'GET',
'CONTENT_TYPE' => '',
'CONTENT_LENGTH' => '',
'SCRIPT_FILENAME' => '/usr/share/nginx/html/srv_var.php',
'SCRIPT_NAME' => '/srv_var.php',
'PATH_INFO' => '',
'REQUEST_URI' => '/srv_var.php',
'DOCUMENT_URI' => '/srv_var.php',
'DOCUMENT_ROOT' => '/usr/share/nginx/html',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'GATEWAY_INTERFACE' => 'CGI/1.1',
'SERVER_SOFTWARE' => 'nginx/1.4.4',
.....
)
I can not figure where the problem is. Any ideas?

I stumbled across a solution to this. The $fastcgi_path_info var works together with $fastcgi_split_path_info, and needs to be set within the location block. Here's what worked in our environment:
location ~ [^/]\.php(/|$) {
root /var/www/jurism-php;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
}
There is also an example in the Nginx documentation under fastcgi_split_path_info.
(... which I now see matches more than one post above. Possibly the PATH_INFO line needs to be set after the include statement, to avoid clobbering the value.)

Debug with PHP
First of all, in modern PHP, the PATH_INFO is stored in the $_SERVER array. Try:
echo "called SCRIPT_NAME: {$_SERVER['SCRIPT_NAME']} with PATH_INFO: {$_SERVER['PATH_INFO']}";
In any case phpinfo() comes to the rescue to help find a lot of the internal php information, like variables and configurations.
Nginx config
As for the NginX config most of it is already explained in the other posts. So this here is a summary and a closer look at the details and the why of the following sample location block:
location /main.php {
# regex to split $uri to $fastcgi_script_name and $fastcgi_path_info
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
# Check that the PHP script exists before passing it
try_files $fastcgi_script_name =404;
# Bypass the fact that try_files resets $fastcgi_path_info
# see: http://trac.nginx.org/nginx/ticket/321
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;
# set the standard fcgi paramters
include fastcgi.conf;
# pass the request to the socket
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
Explanation line-by-line
The fastcgi_split_path_info splits your location between SCRIPT_NAME and PATH_INFO.
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
The expression in the first parentheses of the regular-expression extracts the SCRIPT_NAME, while the second extracts the PATH_INFO.
Recap on regular-expressions
The first regex group, (.+?\.php), expects any character (the dot .), at least once or more than once (the plus +). with a trailing .php. The dot in .php is escaped to \.php so it's taken literally not as "any character".
The questionmark ? makes the plus lazy (+?) so the evaluation stops at the first .php suffix.
E.g. - /some.php/next.php/path-info is evaluated to a SCRIPT_NAME of /some.php with a PATH_INFO of /next.php/path-info; beware, not to a SCRIPT_NAME of /some.php/next.php with a PATH_INFO of /path-info.
The second regexp group, (/.*), basically takes everything that start with a slash as PATH_INFO.
The leading ^ and trailing $ bind the expressions to the start and end of the line.
The next line checks that the extracted script really does exist as a file:
try_files $fastcgi_script_name =404;
Otherwise it returns a 404 error. This prevents giving non-existing files to the PHP processor, however has the bad habit of resetting the $fastcgi_path_info variable (see: http://trac.nginx.org/nginx/ticket/321).
One workaround is to store $fastcgi_path_info in $path_info and set the FCGI param to the stored $path_info. This is done by the next two lines:
# Bypass the fact that try_files resets $fastcgi_path_info
# see: http://trac.nginx.org/nginx/ticket/321
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;
The other FCGI parameters are then set within the include of fastcgi.conf. This file, that's sometimes also named fastcgi_params should be provided by your distribution.
include fastcgi.conf;
Then finally pass the request to your current PHP instance socket (here PHP 7.4):
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
Conclusion
Now remember that all of this happens only, if the surrounding location block is hit. The above example is a prefix location, meaning that every location is matched, that starts with the prefix /main.php. This would be a typical configuration for a routed PHP application that has only one central file named main.php. To catch all .php files a regex has to be used, which could be as simple as ^.+?\.php(/|$). The (/|$) after the .php means that there's either a slash (and more characters) or nothing after the .php part of the location. Subdirectories are also allowed, so the expression matches basically every location that somewhere contains the string .php, as long as it's either at the end or followed by a slash.
location ~ ^.+?\.php(/|$) {
#...
}
As the location is only the guard that allows entering the following block, the final PHP filename and path-info are still split as described above. If the resulting filename does not exist a 404 is returned.
This is just a simple configuration. Of course there's a myriad of possibilities to configure the location regex, to suit the needs of your specific application. To go into all that details would be a small book.

My working configuration is as follows:
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+\.php)($|/.*);
try_files $fastcgi_script_name =404;
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_ignore_client_abort off;
}

Try this:
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;
http://wiki.nginx.org/PHPFcgiExample
http://trac.nginx.org/nginx/ticket/321

For people getting here, reading things..
The problem seems to be that the regular expression (regex) in
location ~* ^/~(.+?)(/.*\.php)$ {
will never match a uri that does not end on .php, so the other regex will never "catch" anything in the last capturing group.
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
So changing that first regex to something like the following will "fix" that.
location ~ [^/]\.php(/|$) {

Late answer but it might be useful to someone.
I used the variable REQUEST_URI instead of PATH_INFO. Looks like it contains the same value as PATH_INFO is supposed to have.

here is what i got. and it works like a charm.
nginx 1.10.1
php 5.6.24
https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/

I have come across this issue but my scenario is slightly different as I use try_files in my directive. Here is my config along with technical explanations:
This is what my location block looks like
location / {
include php-fpm.conf;
try_files $uri $uri/ /index.php =404;
}
and php-fpm.conf
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+?\.php)(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
set $path_info $fastcgi_path_info;
include fastcgi.conf;
fastcgi_param PATH_INFO $path_info;
Two special notices here:
I installed nginx via brew and it did not contain PATH_INFO param so I had to add it manually (taken from here)
fastcgi_param PATH_INFO $fastcgi_path_info;
using try_files is a special case (source)
The ​try_files directive changes URI of a request to the one matched on the file system, and subsequent attempt to split the URI into $fastcgi_script_name and $fastcgi_path_info results in empty path info - as there is no path info in the URI after try_files.
so what we do is save INFO_PATH to a temporary variable and then set INFO_PATH using that temporary variable

Related

How to write an NGINX rewrite which strips out everything from the URL after /filters/

We're running a Symfony application, which has a route of:
domain.tld/main-category/category/
For SEO purposes, they want to include filters in the URL like this:
domain.tld/main-category/category/filters/price:0-100
So basically we want the URL in the browser to remain the same, but in Symfony it should strip out everything after and including /filters/. So the route should remain "domain.tld/main-category/category/".
I tried this which does work, however I need it to work using Symfony routes but could not get it to work.
rewrite ^/.*/filters/.* /test.html last;
NGINX config:
location ~ ^/index\.php(/|$) {
include fastcgi_params;
fastcgi_pass php_project:9000;
fastcgi_read_timeout 600s;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
}
DISCLAIMER: This is a bit of a guess and might not work for you
PHP applications such as index.php often use the REQUEST_URI parameter to determine the "route". This is defined within your fastcgi_params file as:
fastcgi_param REQUEST_URI $request_uri;
One solution could be to manipulate this value using a map directive.
For example:
map $request_uri $myroute {
default $request_uri;
~*^(?<newroute>/.*)/filters/ $newroute;
}
server {
...
location ~ ^/index\.php(/|$) {
include fastcgi_params;
fastcgi_pass php_project:9000;
fastcgi_read_timeout 600s;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param REQUEST_URI $myroute;
internal;
}
}
Note that the map must be placed outside of the server block.
You do not need to edit the fastcgi_params file, simply ensure that the fastcgi_param statements appear in the location block after the include statement.

Nginx, location, alias and php

We are moving from lighttpd to nginx for HTTP/2.0 and having hard time configuring something which was so simple in lighttpd.
alias.url = ("/api" => "/web/myserver.com/develapi/")
We do this as we version control api and then just point url to appropriate folder. We do not want to use file links as well. Moreover, it's an one line command in lighttpd.
Here is how we are trying to achieve the same in NGINX.
location /api {
alias /web/myserver.com/develapi;
location ~ \.php$ {
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
}
}
However, this does not work as nginx passes /web/myserver.com/develapi as $document_root and /api/index.php as $fastcgi_script_name and hence resulting SCRIPT_FILENAME is wrong.
We then tried to use root
location /develapi {
root /web/myserver.com;
location ~ \.php$ {
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
}
}
This works as nginx passes /web/myserver.com as $document_root and /develapi/index.php $fastcgi_script_name and hence resulting SCRIPT_FILENAME is correct.
However, using root means either we have change existing structure or url. For this situation, using alias is the right solution but the way nginx implementing it, makes it tricky to use alias.
May be we are missing something trivial, any pointers will be appreciated.
I think problem is commonly answered/documented way to return SCRIPT_FILENAME
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
Reading more docs reveals $request_filename is a better way to go and works in both root and alias
fastcgi_param SCRIPT_FILENAME $request_filename;
It has been 3+ years... hope you have found the answer by now!
Here is a solution, for similar problem where I wanted to rewrite the fastcgi_script_name stripping off "wiki/" in front of them.
location /wiki {
alias C:/opt/mw/mediawiki-1.36.1;
index index.html index.php index.htm;
location ~ ^/wiki(/.*\.php(?:\?.*)?)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$1;
include fastcgi_params;
}
}
Got the inspiration from: How to remove a path segment from NGiNX fastcgi_script_name?

What is fastcgi_index in nginx used for?

On many sites can be found this nginx location block :
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000
fastcgi_index index.php
...
}
Given the official documentation of fastcgi_index, it seems like it is used when requests end with /. However, it doesn't match the regular expression of the location block above? Am I missing something about the fastcgi_index directive?
You are right, if your nginx configuration (outside the location directive) has no index directive, then the location directive will never match and the fastcgi_index directive is useless.
If you have a line like this on your configuration
index index.php
then a request to / will create an internal redirect to /index.php, the location will match and fastcgi will be called. php-fpm will need a SCRIPT_FILENAME parameter that points to the file being executed. Normally, the configuration looks something like this:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
$fastcgi_script_name contains the name of the matched script, so fastcgi_index is ignored.
There is at least one instance where fastcgi_index is useful and used: when nginx and php-fpm are on different servers and nginx can't match the index.php file.

NGINX location rewrite

Given the URL below, how can I have NGINX automatically pass the last part of the URL (not the GET Params but the last chunk of the base URL - myMap) to fastcgi_param SCRIPT_FILENAME?
The URL:
http://localhost/mapserver/myMap?&LAYERS=base....
NGINX config:
location /mapserver/ {
fastcgi_pass unix:/tmp/mapserver.sock;
fastcgi_index mapserv?*;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/mapserv?map=/mapfiles/myMap.map;
include fastcgi_params;
}
Thanks.
The documentation states that you can use variables in fastcgi_param values :
Sets a parameter that should be passed to the FastCGI server. The value can contain text, variables, and their combination. These directives are inherited from the previous level if and only if there are no fastcgi_param directives defined on the current level.
So you can use a location with a regular expression and a capture group :
location ~ /mapserver/(.*)$ {
fastcgi_pass unix:/tmp/mapserver.sock;
fastcgi_index mapserv?*;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/mapserv?map=/mapfiles/$1.map;
include fastcgi_params;
}
Be aware that this kind of location has a particular priority during request processing.

fastcgi - passing information to scripts

I`m new with nginx and I dont know even what should I look for. My task is to force (I think it is) fastcgi to pass information (PATH_INFO) to the scripts. I have been told that I should add something in block after location ~ .php$ . Adding before fastcgi_pass xxx;
fastcgi_param PATH_INFO $fastcgi_path_info;
would made that happened ?
My location part config file looks like that:
location ~ ^/backend\.php/(.*)$ {
try_files $uri /backend.php?$1;
}
location ~ \.php$ {
# Zero-day exploit defense.
# http://forum.nginx.org/read.php?2,88845,page=3
# Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi.
# Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine. And then cross your fingers that you won't get hacked.
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass xxx;
}

Resources