How to Configure Nginx to Support the Requests to PHP Programs

Nginx, as we know, can be used as a web server or HTTP server, but it can not interpret PHP code. To enable users to request PHP programs, including basic scripts and complex applications, the Nginx FastCGI module must route requests to the PHP FastCGI server, FPM (FastCGI Process Manager). Nginx will respond to the user with the processing result.

This post will go through how to configure Nginx so that it can work with FPM to handle client requests to PHP programs in detail.

1 Install Nginx and PHP

If you have Nginx and PHP installed on your machine and PHP FPM is running, you can skip this step.

Nginx and PHP can be installed via a package manager (such as Yum/DNF for CentOS/RHEL or APT for Ubuntu) or by manually compiling the source code. The former is fast and convenient, but it does not support feature customization and the software version may lag; the latter is much more complicated, but it can use the latest version and does support feature customization to some extent. You can select either one based on your requirements.

To compile and install the latest Nginx, please consult:

To compile and install the latest PHP, please consult:

The location of the Nginx configuration file differs depending on the operating system and installation technique used. The Nginx configuration file in this article is located at:

/usr/local/nginx/conf/nginx.conf

To allow Nginx to connect with FPM, either a Unix domain socket (such as /tmp/php-fpm.sock) produced by FPM or a Network socket (such as 127.0.0.1:9000) will be used. The value of the listen directive in the FPM configuration file www.conf can be used to determine which one to utilize.

Before proceeding, please ensure that both Nginx and PHP are running and that the nginx command from Nginx is also available for execution.

2 Handle PHP Request

If we have a virtual server set up by server block directive as below from Nginx configuration, when requesting the server by the name from server_name directive, which is usually a domain name (here is local host name localhost), static files in the web directory, the directory set by root directive (here is /home/www-data/www), are accessible.

user www-data;
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;

    server {
        listen 80;
        server_name localhost;

        location / {
            root /home/www-data/www;
            index index.html index.htm;
        }
    }
}

In such configuration, only static files are accessible to client from Nginx. If requesting a PHP file, the file will be sent to client as itself with no interpretation by FPM (the response performs differently depending on the value of default_type directive in Nginx configuration, for example, if the value is set to “application/octet-stream”, the PHP file will be downloaded; if the value is “text/plain”, the file will be displayed as plain text).

In order to prevent PHP files from being treated as regular files, we need to modify this configuration. We need to add a new block directive location as below in the server block directive to filter out requests for PHP files and pass them to FPM for processing.

location ~ \.php$ {
    root /home/www-data/www;
    fastcgi_pass unix:/tmp/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

Another syntax other than the prefix string of location block directive is used here, that is a regular expression specified by the case-sensitive preceding modifier ~, meaning that if the Request-URI [1] except for the query string part[2] matches this regular, the corresponding block directive will be used to handle the request.

The regular expression we set here is \.php$, which means when request URI ends with “.php”, the newly added block directive is used for processing. Note that the location with regular expression has higher priority than the location with prefix string in Nginx. Therefore, Nginx will try to match the newly added location first, and then try to match the old location if it fails.

The newly added block directive location contains 4 directives, of which fastcgi_pass and fastcgi_param belong to the FastCGI module of Nginx, which are also the core directives to achieve our purpose. We will explain these directives one by one in the following part.

The 1st is root directive, which defines the directory to store PHP files. In our example, PHP files are in the same place as other web files, so this value is same as the one in old root directive. If this is repetitive to you, you can put root directive in the block directive of server, and remove the root directive from both location, letting them inherit the value from server block directive.

The 2nd is fastcgi_pass directive, which defines the address of FastCGI server (FPM). It can be either a Unix socket or a Network socket, which are the two ways for Nginx to communicate with FPM as mentioned at the beginning of this article. In this example, we used the first one and its format is “unix:/tmp/php-fpm.sock”. You can also choose the second way.

The 3rd is fastcgi_param directive, which defines the parameters sent to FastCGI server (FPM). In our example, only one parameter SCRIPT_FILENAME is set, which is used to specify the name of requested PHP file. Its value is conjuncted by the two built-in variables from two Nginx modules. Of the two variables, $document_root refers to the directory specified by root directive, and $fastcgi_script_name refers to the request URI. For example, when client requests to access “http://localhost/test.php”, Nginx can find the PHP file, if it exists, via path “/home/www-data/www/test.php”.

The 4th is include directive, which is used to include the pre-canned Nginx file fastcgi_params in the current context. This file includes many parameters specified by fastcgi_param directive (like QUERY_STRING, REQUEST_METHOD, etc), covering as much information that may be used in request handling as possible. Note that the SCRIPT_FILENAME parameter that we set previously is also included in this pre-canned file, but the parameter will be overridden by the parameter of same name set in the context in which it was included.

After the new location block directive is added, the server block directive in Nginx configuration file should look like this:

server {
    listen 80;
    server_name localhost;

    location / {
        root /home/www-data/www;
        index index.html index.htm;
    }

    location ~ \.php$ {
        root /home/www-data/www;
        fastcgi_pass unix:/tmp/php-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Now you may create a simple PHP script to test whether the newly modified configuration file is working. First, create a PHP file called index.php in the directory of web files (/home/www-data/www). The file content is as below:

<?php echo "Hello,{$_GET['name']}!\n";

Then execute below command to test the Nginx configuration file to make sure the modification is correct:

sudo nginx -t

If no error, run following command to reload the updated configuration file:

sudo nginx -s reload

At last, use following command to send a request to Nginx:

curl http://localhost/index.php?name=linuxfere

If everything is fine, you should see the words “Hello,linuxfere!” in terminal, which means Nginx has successfully passed the request to FPM, and successfully received the processing result from PHP.

3 Support Automatic indexing

So far, although Nginx can handle the request from client for PHP files, there is still one problem, that is the request URI must end with “.php” to match the location block directive to process PHP, otherwise it will be treated as a static file or directory in processing.

If we remove the “index.php” from the previous request address like below:

curl http://localhost/?name=linuxfere

The location block directive that used to handle the static files will be matched:

location / {
    root /home/www-data/www;
    index index.html index.htm;
}

Since the request URI does not specify a file, only a slash character(/) indicating the root directory of requested web server, Nginx will try to find the two files, index.html and index.htm that specified by index directive, in the directory set by root directive. If they do not exist, a 403 response will be returned, indicating that there is no index file available.

In order for index.php to be indexed by Nginx, it needs to be added to index directive:

index index.php index.html index.htm;

In this way, when the request URI is a directory, Nginx will check the index.php under this directory automatically. Once the index directive is found, an internal redirect will be made to pass the request for index.php to the location block detective for PHP processing to handle.

After above alteration, the Nginx configuration file should now look like:

user www-data;
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;

    server {
        listen 80;
        server_name localhost;

        location / {
            root /home/www-data/www;
            index index.php index.html index.htm;
        }

        location ~ \.php$ {
            root /home/www-data/www;
            fastcgi_pass unix:/tmp/php-fpm.sock;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }
}

In such configuration, if the request ends with “.php”, it will be passed to the second location block directive to process, otherwise it will be processed in the first location block directive. If the requested file exists, it will be sent directly, otherwise Nginx will try to find the file named index.php or index.html or index.htm one after another. If index.php is found, the request will be internally redirected to the second location block directive to process, otherwise Nginx will keep looking for index.html or index.htm. If it exists, Nginx will internally redirect the request to the first location block directive to process, otherwise return a 403 (if request URI ends with slash /) or 404 response.

Save the updated configuration file, and execute below command to let Nginx reload the configuration:

sudo nginx -s reload

Now send the same request to Nginx again, and Nginx will respond properly:

curl http://localhost/?name=linuxfere

In case that one PHP file corresponds to one page, current configuration file is quite enough to realize our requirement. However, if your PHP programs use single point of entry strategy, appropriate modifications still need to be done to make it work.

4 Support Single Point of Entry

Many modern PHP frameworks (such as Symfony, Laravel, etc.) adopt a “single point of entry” strategy, that is, the client’s request must reach the application through a single entry (such as index.php). Frameworks will use the “routing” mechanism to match the client’s request, and perform the corresponding functions of application.

To make it easy to explain, we will replace the content of index.php to following code:

<?php

$route = '/user';
$request_uri = $_SERVER['REQUEST_URI'];

if (str_starts_with($request_uri, $route)) {
    if (isset($_GET['name'])) {
        $name = $_GET['name'] ?: 'Stranger';
        echo "Hello, {$name}!"; exit;
    }
}

http_response_code(404);
echo '404 Not Found';

The simple PHP script here simulates a PHP application with single point of entry strategy. It defines only one route /user. If the request reaches the application and the request URI matches this route, a welcome message with the user name will be displayed, otherwise a 404 page will be displayed.

For the current Nginx configuration, however, if you make a request like below:

curl http://localhost/user?name=linuxfere

It will not reach the application. This is because Nginx does not know how to handle this request. It only gets that the request URI does not end with “.php”, and there is no file named “user” in the directory specified by root directive, so it will eventually return a 404 response.

To solve this problem, we need to make sure that the client’s request can reach the application through the single point of entry “index.php”, even if all conditions are not satisfied, so that the application can decide whether the current request matches the defined route, and then make the correct response.

Here we need to use try_files directive, which belongs to the Module ngx_http_core_module of Nginx. It specifies multiple files or directories separated by space as parameters, and the last parameter is a reserve request URI. Nginx will check the existence of these files or directories in order. If a file exists, it will be processed in the current context. If none of the files exist, an internal redirect to the last parameter will be made (note that the last parameter must be a valid request URI, otherwise a loop internal redirect will be made to the current location, resulting in a 500 response).

In order to access application properly, we need to add below try_files directive to the first block directive of location:

try_files $uri $uri/ /index.php?$args;

In this directive, two built-in variables from Nginx ngx_http_core_module are also used: $uri and $args. The former refers to the part without arguments from the current request URI, that is /user; the later refers only to the arguments part in current request URI, that is name=linuxfere.

Here we put the try_files directive in location block directive as below:

location / {
    root /home/www-data/www;
    index index.php index.html index.htm;
    try_files $uri $uri/ /index.php?$args;
}

For now, the location block directive indicates: First search for the file named $uri in the directory set by root directive. If it exists then response; if it is not found, then add a slash after $url, use it as a directory for indexing, and search for the files indicated by index directive in order. If the index file exists, internally redirect to the current location to response; if no file is indexed, then internally redirect to the request URI indicated by the last parameter, that is “/index.php?args”. Therefore, when the requested static file does not exist, or when the requested directory can not index any files, the request will automatically enter the application’s single point of entry index.php.

The following is the final content of Nginx configuration file. This configuration applies to not only single page PHP files, but also PHP applications that use a single point of entry strategy, like WordPress, Symfony, Laravel, etc.

user www-data;
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;

    server {
        listen 80;
        server_name localhost;

        location / {
            root /home/www-data/www;
            index index.php index.html index.htm;
            try_files $uri $uri/ /index.php?$args;
        }

        location ~ \.php$ {
            root /home/www-data/www;
            fastcgi_pass unix:/tmp/php-fpm.sock;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }
}

Save the updated configuration file, and execute below command to let Nginx reload the configuration:

sudo nginx -s reload

Now request the same URL again, the application should work:

curl http://localhost/user?name=linuxfere

Since Nginx can not find a file or directory named user, it will internally redirects to the last request URI, which is /index.php?name=linuxfere. The request after internal redirect will match the location block directive that is used to handle PHP programs, and enter the application through the single point of entry. The application will use the route /user to compare the original request URI, /user?name=linuxfere. If the URI starts with “/user”, it will take the value linuxfere of name in the query string, and combine into the welcome message “Hello, linuxfere!“.

Please note that, the query string and the original request URI are passed to the application by Nginx fastcgi_param directive via QUERY_STRING and REQUEST_URI parameters.


[1] The term Request-URI here is defined by HTTP specification (RFC 2616,§5.1.2).
[2] As specified by Nginx documentation, the location directive only tries to match the part of Request-URI excluding the query string.

Leave a comment

Your email address will not be published. Required fields are marked *