FAQ

Page Discussion History

Difference between revisions of "PHPFcgiExample"

(Connecting Nginx to the running FastCGI Process)
(grammar corrections + added "fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;" to the params file)
 
(9 intermediate revisions by 6 users not shown)
Line 1: Line 1:
= FastCGI Example =
+
= FastCGI Example (with PHP FPM) =
 +
 
 +
This example is for newer PHP (>= 5.3.3) using the included PHP FPM (FastCGI Process Manager), for older PHP see [[PHPFcgiExampleOld]].
 +
 
 +
This guide assume PHP FPM already installed and configured either using tcp port ('''127.0.0.1:9000''') or unix socket ('''/var/run/php-fpm.sock''').
 +
 
 +
There are many guide about configuring nginx with PHP FPM,
 +
but many of them are incomplete (''doesn't handle PATH_INFO correctly'')
 +
or contain security issues (''doesn't check whether the script is indeed php file'').
 +
 
 +
== FastCGI Params ==
  
 
First thing, I recommend keeping all your typical FCGI settings in a single file and importing them.
 
First thing, I recommend keeping all your typical FCGI settings in a single file and importing them.
  
For example you might have an /etc/nginx/fastcgi.conf  (or /etc/nginx/fastcgi_params: installed by default on debian) file that looks like this:
+
For example on debian and ubuntu by default there is '''/etc/nginx/fastcgi_params''' file that should looks like this:
  
 
<geshi lang="nginx">
 
<geshi lang="nginx">
#fastcgi.conf
+
fastcgi_param   QUERY_STRING           $query_string;
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
+
fastcgi_param   REQUEST_METHOD         $request_method;
fastcgi_param  SERVER_SOFTWARE    nginx;
+
fastcgi_param   CONTENT_TYPE           $content_type;
fastcgi_param QUERY_STRING       $query_string;
+
fastcgi_param   CONTENT_LENGTH         $content_length;
fastcgi_param REQUEST_METHOD     $request_method;
+
fastcgi_param CONTENT_TYPE       $content_type;
+
fastcgi_param CONTENT_LENGTH     $content_length;
+
fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
+
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
+
fastcgi_param  REQUEST_URI        $request_uri;
+
fastcgi_param  DOCUMENT_URI      $document_uri;
+
fastcgi_param  DOCUMENT_ROOT      $document_root;
+
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
+
fastcgi_param  REMOTE_ADDR        $remote_addr;
+
fastcgi_param  REMOTE_PORT        $remote_port;
+
fastcgi_param  SERVER_ADDR        $server_addr;
+
fastcgi_param  SERVER_PORT        $server_port;
+
fastcgi_param  SERVER_NAME        $server_name;
+
</geshi>
+
  
This allows you to keep your individual FastCGI location blocks as simple as possible.
+
fastcgi_param  SCRIPT_FILENAME        $document_root$fastcgi_script_name;
 +
fastcgi_param  SCRIPT_NAME            $fastcgi_script_name;
 +
fastcgi_param  PATH_INFO              $fastcgi_path_info;
 +
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
 +
fastcgi_param  REQUEST_URI            $request_uri;
 +
fastcgi_param  DOCUMENT_URI            $document_uri;
 +
fastcgi_param  DOCUMENT_ROOT          $document_root;
 +
fastcgi_param  SERVER_PROTOCOL        $server_protocol;
  
=== Spawning a FastCGI Process ===
+
fastcgi_param  GATEWAY_INTERFACE      CGI/1.1;
Unlike Apache or Lighttpd, Nginx does not automatically spawn FCGI processes.  You must start them separately.  In fact, FCGI is a lot like proxying.  There's a few ways to start FCGI programs, but luckily PHP5 will auto-spawn as many as you set in the PHP_FCGI_CHILDREN environment variable. First, install the <tt>php5-cgi</tt> library. Then, we can simply run <tt>php-cgi -b 127.0.0.1:9000</tt> manually, or create an init script like this:
+
fastcgi_param  SERVER_SOFTWARE        nginx/$nginx_version;
  
<geshi lang="bash">
+
fastcgi_param  REMOTE_ADDR            $remote_addr;
#!/bin/bash
+
fastcgi_param  REMOTE_PORT            $remote_port;
BIND=127.0.0.1:9000
+
fastcgi_param  SERVER_ADDR            $server_addr;
USER=www-data
+
fastcgi_param  SERVER_PORT            $server_port;
PHP_FCGI_CHILDREN=15
+
fastcgi_param  SERVER_NAME            $server_name;
PHP_FCGI_MAX_REQUESTS=1000
+
  
PHP_CGI=/usr/bin/php-cgi
+
fastcgi_param  HTTPS                  $https;
PHP_CGI_NAME=`basename $PHP_CGI`
+
PHP_CGI_ARGS="- USER=$USER PATH=/usr/bin PHP_FCGI_CHILDREN=$PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS=$PHP_FCGI_MAX_REQUESTS $PHP_CGI -b $BIND"
+
RETVAL=0
+
  
start() {
+
# PHP only, required if PHP was built with --enable-force-cgi-redirect
      echo -n "Starting PHP FastCGI: "
+
fastcgi_param   REDIRECT_STATUS        200;
      start-stop-daemon --quiet --start --background --chuid "$USER" --exec /usr/bin/env -- $PHP_CGI_ARGS
+
      RETVAL=$?
+
      echo "$PHP_CGI_NAME."
+
}
+
stop() {
+
      echo -n "Stopping PHP FastCGI: "
+
      killall -q -w -u $USER $PHP_CGI
+
      RETVAL=$?
+
      echo "$PHP_CGI_NAME."
+
}
+
 
+
case "$1" in
+
    start)
+
      start
+
   ;;
+
    stop)
+
      stop
+
  ;;
+
    restart)
+
      stop
+
      start
+
  ;;
+
    *)
+
      echo "Usage: php-fastcgi {start|stop|restart}"
+
      exit 1
+
  ;;
+
esac
+
exit $RETVAL
+
 
</geshi>
 
</geshi>
  
Save this to <tt>/etc/init.d/</tt> (or wherever your init scripts are) as <tt>php-fcgi</tt>. Install the usual way (e.g. for Debian/Ubuntu: <tt>update-rc.d php-fcgi defaults</tt>) and start it.
+
Please note if you're using Ubuntu Precise (12.04), I change '''SCRIPT_FILENAME''' and add '''PATH_INFO''' params.
  
=== Using a Unix Socket ===
+
== Connecting nginx to PHP FPM ==
  
The above example binds PHP to run on port 9000 on localhost (127.0.0.1:9000). This is the most common and easiest to understand option. Unix sockets are however much better. To use them you need to do two things.
+
Now we must tell Nginx to proxy requests to PHP FPM via the FCGI protocol:
 
+
# In the above script change "BIND=127.0.0.1:9000" to "BIND=/tmp/php.socket"
+
# In the Nginx config change "fastcgi_pass 127.0.0.1:9000;" to ""fastcgi_pass unix:/tmp/php.socket;"
+
 
+
=== Connecting Nginx to the running FastCGI Process ===
+
Now that the FCGI process is running, we must tell Nginx to proxy requests to it via the FCGI protocol:
+
  
 
<geshi lang="nginx">
 
<geshi lang="nginx">
location ~ \.php$ {
+
        location ~ [^/]\.php(/|$) {
  # Filter out arbitrary code execution
+
                fastcgi_split_path_info ^(.+?\.php)(/.*)$;
  location ~ \..*/.*\.php$ {return 404;}
+
                if (!-f $document_root$fastcgi_script_name) {
  include fastcgi_params;
+
                        return 404;
  fastcgi_pass 127.0.0.1:9000;
+
                }
}
+
 
 +
                fastcgi_pass 127.0.0.1:9000;
 +
                fastcgi_index index.php;
 +
                include fastcgi_params;
 +
        }
 
</geshi>
 
</geshi>
  
Restart Nginx.
+
If you're using unix socket change <code>fastcgi_pass</code> to:
 
+
=== Secure your upload directory!! ===
+
 
+
Too many example configs fail to secure the "uploads" directory of the application.  Remember that if someone can upload a file named xyz.php and the uploads dir is publically accessible then you have given the attacker an easy way to insert PHP onto your site...
+
 
+
So if your app has an upload dir "/images/" then insert if ($uri !~ "^/images/") before fastcgi_pass, as so:
+
 
+
 
<geshi lang="nginx">
 
<geshi lang="nginx">
location ~ \.php$ {
+
                fastcgi_pass unix:/var/run/php5-fpm.sock;
  include /etc/nginx/fastcgi_params;
+
  if ($uri !~ "^/images/") {
+
    fastcgi_pass 127.0.0.1:9000;
+
  }
+
}
+
 
</geshi>
 
</geshi>
  
=== Common Errors ===
+
Restart nginx.
  
If the following error is returned to you when you try to visit a PHP file, it is because FastCGI does not know where your document_root is located.
+
== Testing ==
  
<geshi lang="text">
+
Create '''test.php''' on nginx document root containing just:
No input file specified.
+
<geshi lang="php">
 +
<pre><?php var_export($_SERVER)?></pre>
 
</geshi>
 
</geshi>
  
First, check to make sure your FCGI parameter for SCRIPT_FILENAME is set to include the $document_root variable. The current fastcgi_params file provided by the debian package does not include the document root in the script filename parameter. It should look like the example below.
+
In the browser try to request:
 +
# /test.php
 +
# /test.php/
 +
# /test.php/foo
 +
# /test.php/foo/bar.php
 +
# /test.php/foo/bar.php?v=1
  
<geshi lang="nginx">
+
Pay attention to the value of REQUEST_URI, SCRIPT_NAME, PATH_INFO and PHP_SELF.
# Located with your other FastCGI parameters, possibly in:
+
# /etc/nginx/fastcgi.conf or /etc/nginx/fastcgi_params
+
fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
+
</geshi>
+
  
If this is not the problem, then your nginx server configuration is probably missing the document root setting (or it is misplaced). This setting is configured through the "root" variable. Below is an example of a properly configured server block. Notice that the root is '''outside''' of the location block.
+
Here's the correct output for http://lemp.test/test.php/foo/bar.php?v=1 :
 +
<pre>
 +
array (
 +
  'USER' => 'www-data',
 +
  'HOME' => '/var/www',
 +
  'FCGI_ROLE' => 'RESPONDER',
 +
  'QUERY_STRING' => 'v=1',
 +
  'REQUEST_METHOD' => 'GET',
 +
  'CONTENT_TYPE' => '',
 +
  'CONTENT_LENGTH' => '',
 +
  'SCRIPT_FILENAME' => '/var/www/test.php',
 +
  'SCRIPT_NAME' => '/test.php',
 +
  'PATH_INFO' => '/foo/bar.php',
 +
  'REQUEST_URI' => '/test.php/foo/bar.php?v=1',
 +
  'DOCUMENT_URI' => '/test.php/foo/bar.php',
 +
  'DOCUMENT_ROOT' => '/var/www',
 +
  'SERVER_PROTOCOL' => 'HTTP/1.1',
 +
  'GATEWAY_INTERFACE' => 'CGI/1.1',
 +
  'SERVER_SOFTWARE' => 'nginx/1.4.0',
 +
  'REMOTE_ADDR' => '192.168.56.1',
 +
  'REMOTE_PORT' => '44644',
 +
  'SERVER_ADDR' => '192.168.56.3',
 +
  'SERVER_PORT' => '80',
 +
  'SERVER_NAME' => '',
 +
  'HTTPS' => '',
 +
  'REDIRECT_STATUS' => '200',
 +
  'HTTP_HOST' => 'lemp.test',
 +
  'HTTP_USER_AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:20.0) Gecko/20100101 Firefox/20.0',
 +
  'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
 +
  'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.5',
 +
  'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
 +
  'HTTP_CONNECTION' => 'keep-alive',
 +
  'PHP_SELF' => '/test.php/foo/bar.php',
 +
  'REQUEST_TIME' => 1367829847,
 +
)
 +
</pre>
  
<geshi lang="nginx">
+
== Notes ==
server {
+
        listen  80 default;
+
        server_name  localhost;
+
  
        access_log  /var/log/nginx/localhost.access.log;
+
# The location regex capable to handle '''PATH_INFO''' and properly check that the extension indeed .php (not .phps) whether there is PATH_INFO or not.
        root /var/www/nginx-default;
+
# The '''fastcgi_split_path_info''' regex capable to correctly handle request like '''/test.php/foo/blah.php''' or '''/test.php/'''.
 
+
# The '''if''' lets nginx check whether the *.php does indeed exist to prevent nginx to feeding PHP FPM non php script file (like uploaded image).
        location / {
+
                index  index.html index.htm;
+
        }
+
 
+
        location ~ \.php$ {
+
            include /etc/nginx/fastcgi.conf;
+
            fastcgi_pass unix:/tmp/php.socket;
+
        }
+
}
+
</geshi>
+
  
Once you have updated your nginx configuration, restart nginx and try reloading the PHP page -- FastCGI should now be able to locate the script and interpret it.
+
Some guides recommend to use '''try_files''' instead of '''if''',
 +
if you do that, beware of nginx [http://trac.nginx.org/nginx/ticket/321 bug #321].
 +
I personally think '''if''' is more appropriate for this, even [[IfIsEvil]] agree this is one of the 100% safe thing to use '''if''' with.
  
If this does not solve your problem, see [[pitfalls]] for other common errors.
+
This guide run fine on php.ini '''cgi.fix_pathinfo = 1''' (the default).
 +
Some guide insist to change it to '''cgi.fix_pathinfo = 0''' but doing that make '''PHP_SELF''' variable broken (not equal to '''DOCUMENT_URI''').

Latest revision as of 00:46, 23 December 2013

Contents

FastCGI Example (with PHP FPM)

This example is for newer PHP (>= 5.3.3) using the included PHP FPM (FastCGI Process Manager), for older PHP see PHPFcgiExampleOld.

This guide assume PHP FPM already installed and configured either using tcp port (127.0.0.1:9000) or unix socket (/var/run/php-fpm.sock).

There are many guide about configuring nginx with PHP FPM, but many of them are incomplete (doesn't handle PATH_INFO correctly) or contain security issues (doesn't check whether the script is indeed php file).

FastCGI Params

First thing, I recommend keeping all your typical FCGI settings in a single file and importing them.

For example on debian and ubuntu by default there is /etc/nginx/fastcgi_params file that should looks like this:

fastcgi_param   QUERY_STRING            $query_string;
fastcgi_param   REQUEST_METHOD          $request_method;
fastcgi_param   CONTENT_TYPE            $content_type;
fastcgi_param   CONTENT_LENGTH          $content_length;
 
fastcgi_param   SCRIPT_FILENAME         $document_root$fastcgi_script_name;
fastcgi_param   SCRIPT_NAME             $fastcgi_script_name;
fastcgi_param   PATH_INFO               $fastcgi_path_info;
fastcgi_param 	PATH_TRANSLATED		$document_root$fastcgi_path_info;
fastcgi_param   REQUEST_URI             $request_uri;
fastcgi_param   DOCUMENT_URI            $document_uri;
fastcgi_param   DOCUMENT_ROOT           $document_root;
fastcgi_param   SERVER_PROTOCOL         $server_protocol;
 
fastcgi_param   GATEWAY_INTERFACE       CGI/1.1;
fastcgi_param   SERVER_SOFTWARE         nginx/$nginx_version;
 
fastcgi_param   REMOTE_ADDR             $remote_addr;
fastcgi_param   REMOTE_PORT             $remote_port;
fastcgi_param   SERVER_ADDR             $server_addr;
fastcgi_param   SERVER_PORT             $server_port;
fastcgi_param   SERVER_NAME             $server_name;
 
fastcgi_param   HTTPS                   $https;
 
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param   REDIRECT_STATUS         200;

Please note if you're using Ubuntu Precise (12.04), I change SCRIPT_FILENAME and add PATH_INFO params.

Connecting nginx to PHP FPM

Now we must tell Nginx to proxy requests to PHP FPM via the FCGI protocol:

        location ~ [^/]\.php(/|$) {
                fastcgi_split_path_info ^(.+?\.php)(/.*)$;
                if (!-f $document_root$fastcgi_script_name) {
                        return 404;
                }
 
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_index index.php;
                include fastcgi_params;
        }

If you're using unix socket change fastcgi_pass to:

                fastcgi_pass unix:/var/run/php5-fpm.sock;

Restart nginx.

Testing

Create test.php on nginx document root containing just:

<pre><?php var_export($_SERVER)?></pre>

In the browser try to request:

  1. /test.php
  2. /test.php/
  3. /test.php/foo
  4. /test.php/foo/bar.php
  5. /test.php/foo/bar.php?v=1

Pay attention to the value of REQUEST_URI, SCRIPT_NAME, PATH_INFO and PHP_SELF.

Here's the correct output for http://lemp.test/test.php/foo/bar.php?v=1 :

array (
  'USER' => 'www-data',
  'HOME' => '/var/www',
  'FCGI_ROLE' => 'RESPONDER',
  'QUERY_STRING' => 'v=1',
  'REQUEST_METHOD' => 'GET',
  'CONTENT_TYPE' => '',
  'CONTENT_LENGTH' => '',
  'SCRIPT_FILENAME' => '/var/www/test.php',
  'SCRIPT_NAME' => '/test.php',
  'PATH_INFO' => '/foo/bar.php',
  'REQUEST_URI' => '/test.php/foo/bar.php?v=1',
  'DOCUMENT_URI' => '/test.php/foo/bar.php',
  'DOCUMENT_ROOT' => '/var/www',
  'SERVER_PROTOCOL' => 'HTTP/1.1',
  'GATEWAY_INTERFACE' => 'CGI/1.1',
  'SERVER_SOFTWARE' => 'nginx/1.4.0',
  'REMOTE_ADDR' => '192.168.56.1',
  'REMOTE_PORT' => '44644',
  'SERVER_ADDR' => '192.168.56.3',
  'SERVER_PORT' => '80',
  'SERVER_NAME' => '',
  'HTTPS' => '',
  'REDIRECT_STATUS' => '200',
  'HTTP_HOST' => 'lemp.test',
  'HTTP_USER_AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:20.0) Gecko/20100101 Firefox/20.0',
  'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.5',
  'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
  'HTTP_CONNECTION' => 'keep-alive',
  'PHP_SELF' => '/test.php/foo/bar.php',
  'REQUEST_TIME' => 1367829847,
)

Notes

  1. The location regex capable to handle PATH_INFO and properly check that the extension indeed .php (not .phps) whether there is PATH_INFO or not.
  2. The fastcgi_split_path_info regex capable to correctly handle request like /test.php/foo/blah.php or /test.php/.
  3. The if lets nginx check whether the *.php does indeed exist to prevent nginx to feeding PHP FPM non php script file (like uploaded image).

Some guides recommend to use try_files instead of if, if you do that, beware of nginx bug #321. I personally think if is more appropriate for this, even IfIsEvil agree this is one of the 100% safe thing to use if with.

This guide run fine on php.ini cgi.fix_pathinfo = 1 (the default). Some guide insist to change it to cgi.fix_pathinfo = 0 but doing that make PHP_SELF variable broken (not equal to DOCUMENT_URI).