user3041714
user3041714

Reputation: 333

Nginx + phpFPM: PATH_INFO always empty

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?

Upvotes: 13

Views: 23849

Answers (9)

SND-KNN
SND-KNN

Reputation: 410

Reference: https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_split_path_info

location ~ .+\.php {
    fastcgi_pass    {your_php_fpm_sock_or_server};

    # Some old (custom) frameworks use "PATH_INFO" for routing, requiring the following 2 settings. (e.g. index.php/Home/index)
    fastcgi_split_path_info             ^(.+\.php)(.*)$;
    fastcgi_param   PATH_INFO           $fastcgi_path_info;

    fastcgi_param   SCRIPT_FILENAME     $realpath_root$fastcgi_script_name;

    include         fastcgi_params;
}

Upvotes: 0

Holger Böhnke
Holger Böhnke

Reputation: 1130

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.

Upvotes: 6

Alex
Alex

Reputation: 664

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

Upvotes: -1

imme
imme

Reputation: 645

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(/|$) {

Upvotes: 1

Frank Bennett
Frank Bennett

Reputation: 71

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.)

Upvotes: 7

salihcenap
salihcenap

Reputation: 1937

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;
}

Upvotes: 5

Katarina
Katarina

Reputation: 7

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/

Upvotes: -1

Laila
Laila

Reputation: 1501

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.

Upvotes: 0

Michel Feldheim
Michel Feldheim

Reputation: 18250

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

Upvotes: 1

Related Questions