The nginx logo and php logo in front of a computer screen, next to a checklist
June 27, 2024

How to Configure Nginx for Drupal 10 and PHP-FPM

PHP Development

Nginx is a popular open-source web server software. Paired with Drupal 10 and PHP-FPM, nginx provides powerful performance and stability for load balancing, caching, and more. However, configuring nginx for Drupal 10 is not without its challenges.

In this blog, I explore what happens if you encounter problems with the nginx configuration when installing Drupal 10 while using PHP-FPM. I walk you through solutions and offer insight to ensure you have the necessary knowledge for establishing a robust configuration that works well in a production environment.

Back to top

Getting Started With the Drupal Nginx Configuration Process

The subject of this article, configuring nginx for Drupal and PHP-FPM, evolved out of my efforts at building a lab environment for an upcoming course on Drupal module development. My goal was to create a reproducible set of instructions that could build either a Docker container (e.g., a Dockerfile ), or a VirtualBox VM (e.g., a Vagrantfile ), usable for the course.

I won’t bore you with the details of how I ran build after build until I latched into just the right set of instructions to produce a base environment for Drupal 10. The Dockerfile instructions will later be used to create a Vagrantfile. And yes, before you get started, I know that I’m doing things a little backwards. I should’ve started with a Terraform template, and then I’d already be done! But being the impatient type, and unable to resist the Urge To Code, I barreled on ahead and banged out a suitable Dockerfile. 

It’s based upon the template available as a download from the ZendPHP group. Configuration files for this setup are available on GitHub.

Back to top

Nginx for Drupal 10 Setup

Before diving into the main problem I ran into pertaining to the Drupal 10 nginx using PHP-FPM configuration, let me give you an overview of the container setup.


Using ZendPHP was a no-brainer. The pre-defined Dockerfile served as the basis for the one I ended up using. All I needed were a few additional installation instructions, and I had a container already configured for ZendPHP and PHP-FPM. All management for PHP can now be accomplished using the fabulous zendphpctl script.


Although I have a long and fondly remembered history with Apache, I began toying around with nginx some years ago and got hooked. Accordingly, the setup I’m describing here uses nginx as its web server.


PHP-FPM (PHP FastCGI Process Manager) is a highly efficient alternative PHP FastCGI implementation with features for managing and processing PHP requests. It works by receiving and processing PHP requests from nginx. It can handle high loads and maximize performance by via multiple pools of PHP processes. 


Any intermediate-level PHP developer should be familiar with MariaDB, a powerful open-source MySQL alternative database. In the Drupal installation script I use Drush to do the actual database setup for the new Drupal website.

So now, at long last, we arrive at the entire point of this article: how to properly configure nginx for Drupal 10 and PHP-FPM.

Back to top

Troubleshooting Nginx for Drupal 10 and PHP-FPM Configuration Issues

There are a number of configuration issues you might encounter when configuring nginx for Drupal 10 and PHP-FPM. The configuration problems will be starkly evident the first time you fire up your newly created Drupal 10 installation. 

Let’s tackle the potential issues one at a time and see how to configure nginx for Drupal to avoid problems.

Always Getting the Website Home Page

You check and double-check your configuration. You verify that the Drupal source code has been installed. You check the database. But, no matter what you do, you always end up on the website’s main home page! The most likely culprit is that you didn’t make your new nginx-Drupal configuration available to nginx.

Always getting the home page when configuring nginx for Drupal 10

From a Linux command prompt, here’s what you might see:

root@drupal:/home/training# ls -l /etc/nginx/sites-available/
total 12
-rwxrwxr-x 1 root root  468 Apr 24 09:25 default
-rw-r--r-- 1 root root 2412 May 30  2023 default.old
-rw-r--r-- 1 root root  955 Apr 26 02:02 drupal.conf
root@drupal:/home/training# ls -l /etc/nginx/sites-enabled/ 
total 0
lrwxrwxrwx 1 root root 34 Apr 24 09:29 default →

To fix the problem, create a symbolic link between the available and enabled directories:             

root@drupal:/home/training# ln -s \
   /etc/nginx/sites-available/drupal.conf \
root@drupal:/home/training# ls -l /etc/nginx/sites-enabled/
total 0
lrwxrwxrwx 1 root root 34 Apr 24 09:29 default →
lrwxrwxrwx 1 root root 38 Apr 26 02:11 drupal.conf -> 

The directory defined by the root directive must exist and must be accessible to nginx. Don’t forget to restart nginx!                                 

root@drupal:/home/training# /etc/init.d/nginx restart
 * Restarting nginx nginx                 [ OK ]                            

502 Bad Gateway

If you’re like me, you look for the “official” sources when dealing with configuration. In this case it’s natural to look at the nginx website. Search revealed a community-supplied configuration: the Lando nginx - Drupal configuration. It even addresses PHP-FPM. Hooray! Copy and paste the configuration into /etc/nginx/sites-available/drupal.conf.

But then you get the output shown in the image below.

A 502 Bad Gateway error message

The most likely cause is that PHP-FPM is running on a socket or TCP/IP port that’s different from the one in the nginx documentation. Before you change your nginx configuration, you’ll first need to find how your PHP-FPM installation is configured to “listen.” The listen directive is usually found in the PHP-FPM “pool” configuration. In the case of ZendPHP, this is located in this directory structure (where “X.Y” is your version of PHP):

root@drupal:/home/training# cat \
 /etc/php/X.Y-zend/fpm/pool.d/www.conf |grep listen
listen =

In your presumed drupal.conf  file, look for the fastcgi_pass directive:

root@drupal:/home/training# cat \
  |grep fastcgi_pass
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;

Oops! This doesn’t match. In the demo installation, PHP-FPM is programmed to listen via TCP/IP port 9000 on any IP address whereas nginx thinks that PHP-FPM will respond via a socket unix:/var/run/php/php7.0-fpm.sock. This will not do at all! 

The solution is to rewrite the fastcgi_pass directive to listen on the localhost loopback network


And, of course, don’t forget to restart nginx!

404 Not Found

At first glance you might believe this is a result of an nginx misconfiguration, in my experience, however, this message, especially when you’re just trying to get to the home page, is due to a file system permissions problem. 

A 404 Not Found error message

To start, check the permissions of your Drupal installation:

root@drupal:/home/training# ls -l /var/www 
total 12
drw-r----- 4 root     root   4096 Apr 24 04:57 drupal
drwxrwxr-x 1 www-data zendphp 4096 Apr 24 09:32 html

As you can see, the permissions are set for the root user, and “world” access is disabled. Before changing the permissions, you’ll need to find the users and groups for PHP-FPM and nginx. 

For PHP-FPM look in the “pool” configurations:

root@drupal:/home/training# cat \
   /etc/php/8.2-zend/fpm/pool.d/www.conf \
    | grep -e "user" -e "group"
user = zendphp
group = zendphp

For nginx look in the primary nginx configuration file:

root@drupal:/home/training# cat \
   /etc/nginx/nginx.conf \
    |grep -e "user" 
user www-data;

One approach to reset the appropriate permissions would be to add the user “www-data” to the group “zendphp,” and make an assignment to the group “zendphp.” Alternatively, nginx creates a group of the same name, so you could add the user “zendphp” to the group “www-data,” and assign permissions to the group “www-data.” Either way works fine.

In my example I’ll use a simpler approach which is to  assign both user and group, which covers the needs of nginx and PHP:

root@drupal:/home/training# chown -R www-data:zendphp /var/www/drupal
root@drupal:/home/training# chmod -R 775 /var/www/drupal
root@drupal:/home/training# find /var/www/drupal \
    -type d -exec chmod 775 {} \;
root@drupal:/home/training# find /var/www/drupal \
    -type f -exec chmod 664 {} \;
root@drupal:/home/training# chmod +x /var/www/drupal/vendor/bin/*
root@drupal:/home/training# ls -l /var/www/drupal

total 3156
-rw-rw-r-- 1 www-data zendphp    3553 Apr 24 04:58 composer.json
-rw-rw-r-- 1 www-data zendphp  229522 Apr 24 04:58 composer.lock
-rw-rw-r-- 1 www-data zendphp 2985953 Apr 24 04:57 composer.phar
drwxrwxr-x 28 www-data zendphp    4096 Apr 24 04:58 vendor
drwxrwxr-x 7 www-data zendphp    4096 Apr 24 04:57 web

You’ll note that I needed to add execute permissions to the drupal/vendor/bin folder as I’m using Drush for certain operations.

403 Forbidden

A 403 Forbidden error is returned if you have made a valid request, but the request hasn’t been authorized in the nginx configuration. This means that your Drupal virtual host definition file has identified your Drupal URL (“drupal.local” in this example), and that it’s been properly linked as described in the earlier section.

403 Forbidden error message

In your nginx-Drupal configuration file, look for the primary location block:

location / {
   # some configuration

What’s needed is a try_files directive. You can use the $uri variable to represent the original URI requested:

location / {
   try_files $uri;

However, the problem with this configuration is that only exact matches will be found. So if you enter http://drupal.local/index.php you’ll see your Drupal website. However, if you only enter http://drupal.local you’ll still get the 403 error.

To fix the problem just add another entry, separated with a space, to reference index.php. Append the $query_string variable so that any rejected $uri requests are funneled through index.php, and all original request information will be dragged along:

location / {
   try_files $uri /index.php?$query_string;

CSS and JavaScript Files Not Loading

If you able to access your Drupal site, but aren’t seeing graphics, backgrounds, and styling, it could be a permissions issue, or something as simple has not having enabled a theme. On the other hand, the smart people at nginx added a few extra location blocks you can add to your nginx - Drupal configuration to prevent problems with web assets such as graphics, stylesheets, and JavaScript files.

This snippet does the actual URL rewrite:

    location @rewrite {
        rewrite ^ /index.php; # For Drupal >= 7

These snippets handle specific file types:

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
       try_files $uri @rewrite;
        expires max;
       log_not_found off;
    location ~ ^/sites/.*/files/styles/ {
       try_files $uri @rewrite;

Security Configuration

Some of the other features of the Drupal configuration developed by nginx includes a number of security settings. These are needed because in Drupal the document root points to a directory that allows access to the Drupal source code. Accordingly, you can use these nginx settings to prevent accidentally exposing your source code or other sensitive files to the world.

This prevents users from attempting to access PHP code from higher level directories:

    location ~ \..*/.*\.php$ {
        return 403;


This configuration snippet prevents direct access to files in the /sites directory structure:

    location ~ ^/sites/[^/]+/files/.*\.php$ {
        deny all;


This snippet blocks access to the /vendor folder (used by Composer):

    location ~ /vendor/.*\.php$ {
        deny all;
        return 404;


There are other security settings, however the three mentioned should give you the general idea of what’s available.

Back to top

Final Thoughts

For the most part I’ve found that the Lando nginx - Drupal - PHP-FPM configuration is pretty robust and should work well in a production environment. There are plenty of extra security measures, and it’s flexible enough to serve most purposes. The main things to watch out for are file system permissions issues, misconfigured nginx and PHP-FPM users and groups, webserver permission settings, and configuring the correct listener for PHP-FPM.

Additionally, there are a number of configuration differences between Drupal 10 and earlier versions. There was a major change starting with Drupal 7. The official nginx – Drupal configuration is good at pointing out the differences between Drupal 7+ and earlier versions in its comments.

As you continue to modernize and optimize your Drupal PHP applications, make sure to check back here. Zend continuously offers new training courses, how-to guides, and expert insights for developers, and we are ready to support your team as you tackle tough PHP challenges.

Ready to Expand Your PHP Knowledge?

Whether you're new to PHP or are an advanced developer, Zend PHP training courses are designed to enhance your skills. Learn your way through our instructor-led, on-demand, or free training options.

Find Your Best Course

Back to top

Additional Resources

Back to top