Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nginx + phpFPM: PATH_INFO always empty

Tags:

php

nginx

fastcgi

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?

like image 883
user3041714 Avatar asked Dec 30 '13 22:12

user3041714


1 Answers

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 backslash (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.

like image 178
Holger Böhnke Avatar answered Oct 07 '22 20:10

Holger Böhnke