PHP Errors (via FastCGI) with Nginx on Ubuntu Linode

Solution 1:

Short answer:

The most likely scenario is that you have a slight misconfiguration in Nginx which results in the incorrect path being sent to PHP-FPM. Ensure that your root directive is outside the location block, turn on fastcgi_intercept_errors, increase the verbosity of your error_log (e.g. to notice) and consult those logs for more information.

Long Answer - Diagnosing the problem

Firstly, I am not an Ubuntu person (more of a CentOS one) - so you'll forgive me if a few paths/packages are not quite as specific to your operating system as would be ideal.

As you said, you have multiple parts to your setup that must work together.

  • Nginx - must be able to receive and process a request - the easiest test is a static file.
  • FastCGI - must be able to communicate with PHP.
  • PHP - must be able to successfully interpret your file.

For this approach to diagnostics, we want to keep things simple - don't test it by trying to load a CMS like Wordpress - we want one single independent file.

Test Files:

Static - let's go with a text file called 'test.txt'.

test.txt:

Hello world

PHP - let's go with the phpinfo() function.

test.php:

<?php
    phpinfo();
?>

Testing Nginx:

If Nginx can serve a static file, we can confirm that the basic setup and software is functional.

server {
        listen *:80 default;
        server_name mysite.com www.mysite.com;
        root  /srv/www/mysite.com/public_html;
        error_log /var/log/nginx/mysite.com/error.log notice;
        access_log /var/log/nginx/mysite.com/access.log main;
}

A few points of mention:

  • I have added 'default' to the listen line - this will hopefully ensure that this server block is used (unless of course, you have other server blocks which specify 'default', which is a different problem).
  • I have increased the verbosity of the error_log to 'notice' - I want to see any problems that may arise
  • I have specified an access_log - I want to be able to confirm that any file I try to access either shows up in the access_log or the error_log - nothing unaccounted for.

Copy 'test.txt' into '/srv/www/mysite.com/public_html', ensure that it is readable by the user 'nginx' (which is the default user nginx runs as), permissions of 644 should be sufficient. Ensure that all directories above public_html have 'execute' permissions for 'other' (i.e. 'other' can traverse the directory structure).

Restart Nginx for the configuration changes to take effect (you could reload instead of restarting, if desired).

Test from the same server you have nginx setup on:

curl --header "Host: mysite.com" 127.0.0.1/test.txt

The notable points here:

  • By testing from the same server nginx is setup on, we are able to eliminate DNS and network problems.
  • 127.0.0.1 is of course 'localhost' (but 'localhost' does need a specification in the hosts file to work)
  • Since we are access the site via an IP address, we should tell the server the 'domain name' we are trying to access (not strictly required here since we have our server block setup as the 'default', but good practice).
  • Finally, we need to specify the path to the file - relative to our root directive (from our server block).

Ideally, the above command will return 'Hello world' - the text you entered into your text file.

PHP:

It is fairly easy to ensure that PHP works right:

Create the file 'test.php' (as above), in your public_html folder and run:

php /srv/www/mysite.com/public_html/test.php

You should get a long output that contains all of the information you normally see in on a phpInfo() page.

If the above doesn't work:

  • If you get some sort of file not found error, specify the absolute path to php, and check the path to your file
  • If you get a permissions error, ensure that your current user has the needed permissions - php does not actually require execute permissions on the file when accessed this way.
  • Turn display_errors on and increase the verbosity of error_reporting in your php.ini file (firstly, find the right php.ini file, by using php -i | grep 'Loaded Configuration')

Hopefully by now you have confirmed that PHP, and your simple test file, works.

PHP-FPM:

Unfortunately, FastCGI does not 'speak' plain text. We need an interpreter to help us here. You need the cgi-fcgi binary. (On CentOS it is available in the package 'fcgi', from EPEL; I believe Ubuntu has a package libfcgi that provides the same).

cgi-fcgi reads environment variables and will pass the correct request to our FastCGI process manager (PHP-FPM).

Firstly, let's setup PHP-FPM: The default global options should mostly suffice, however, enable logging - when debugging, we want as much information as we can get our hands on (the default log prefix is /var).

error_log = log/php-fpm.log
log_level = notice

Setup a basic pool, specifying:

[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1

pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2

user = nginx
group = nginx

(Of course, you can - and probably should, use a socket instead of a TCP listener, but I find this approach is easier to test (i.e. fewer permissions problems) - obviously, ensure that there is nothing else listening on the port you choose). We need to permit only the local machine to access it, setup the basics of the process manager, and give the pool an owner (of course, you will change things to suit your needs later on).

  • Start PHP-FPM (address any errors you get on starting it)
    • FPM is a FastCGI process manager for PHP, it is a separate service that runs - typically service php-fpm restart will work (use reload instead if you ever need to do this in a production environment). (There may be an init script for it in /etc/init.d)
  • Install cgi-fcgi if you haven't already

Run the following:

SCRIPT_NAME=/test.php \
SCRIPT_FILENAME=/srv/www/mysite.com/public_html/test.php \
QUERY_STRING= \
REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000

Here we are telling PHP-FPM the path to the file, and the filename, as well as the request type (GET), and then instructing cgi-fcgi to connect to the right host and port.

It should return you the same output you got before, but this time, instead of using the php binary directly, it used FastCGI. If this works, you have successfully validated each component of your setup.

If you get errors, check your error log at /var/log/php-fpm.log

Putting it all together: If each part of the setup works, then, you need to ensure that all the parts can work together. Really, there is only one part left here - getting Nginx to communicate via FastCGI.

In its simplest form, we need to only add a single location block to our existing nginx server block:

    location ~ \.php {
        include /etc/nginx/fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
    }

To support this, the file 'fastcgi_params' is rather essential. The really important lines are:

    fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
    fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;

You can actually include the FastCGI directives you need directly inside the location block - but I find having the external file is far simpler to maintain when you get more complex setups. You'll note here, that the path depends on $document_root - if you don't have that set correctly (the root directive), outside your location block, you will usually run into problems with your setup.

Some setups may also need a line such as:

 fastcgi_split_path_info ^(.+\.php)(/.+)$;

One helpful directive for debugging FastCGI setup errors is fastcgi_intercept_errors On. This will let let errors (such as file not found, etc) be logged by nginx.

Finally, try loading your PHP page through nginx:

curl --header "Host: mysite.com" 127.0.0.1/test.php

Hopefully you get your phpinfo() output. If not, you know that the problem is in your nginx setup (because, every other component works on its own) start checking your nginx error logs, you should have more than enough information logged at this point to identify the problem.