All posts
Tutorials & How-To

How to configure PHP-FPM with NGINX on Ubuntu

Manaal Khan19 June 2026 at 1:21 am7 min read
How to configure PHP-FPM with NGINX on Ubuntu

Key Takeaways

How to configure PHP-FPM with NGINX on Ubuntu
Source:
  • NGINX lacks built-in PHP support; PHP-FPM handles PHP requests via FastCGI over Unix sockets
  • Create separate pools for each site to isolate resources and improve security
  • Use dynamic process management with tuned pm.max_children to balance memory and performance

NGINX does not process PHP on its own. Unlike Apache with mod_php, NGINX serves static files directly and forwards PHP requests to an external process manager. PHP-FPM is that manager, and connecting the two over FastCGI is the standard setup for any PHP application running behind NGINX.

This guide walks through the complete configuration on Ubuntu, covering versions 22.04, 24.04, and 26.04 LTS. You will install PHP-FPM, create a dedicated pool for a site, wire NGINX to that pool via a Unix socket, and test the stack. The 502 Bad Gateway error, the most common failure mode, gets its own troubleshooting section.

What is PHP-FPM and why does NGINX need it?

PHP-FPM stands for FastCGI Process Manager. It is a PHP SAPI (Server API) that spawns a master process listening on a socket or TCP port. Worker processes sit ready to execute scripts. When NGINX receives a request for a .php file, it passes that request to PHP-FPM via the FastCGI protocol. PHP-FPM runs the script and returns the output. NGINX then sends the response to the client.

This architecture separates concerns cleanly. NGINX handles connections, SSL termination, and static assets. PHP-FPM handles PHP execution. Each can be tuned, scaled, or restarted independently.

Which PHP version ships with each Ubuntu release?

Ubuntu's default repositories include different PHP versions depending on the release. Ubuntu 22.04 LTS (Jammy) ships PHP 8.1. Ubuntu 24.04 LTS (Noble Numbat) ships PHP 8.3. Ubuntu 26.04 LTS (Resolute Raccoon) ships PHP 8.5. The commands in this guide use 22.04 as the baseline. Swap version numbers in package names, service names, and socket paths when working on newer releases.

Confirm your environment before editing any config:

bash
lsb_release -rs
php -v

Step 1: Install PHP-FPM

Update your package index and install the FPM package matching your PHP version:

bash
sudo apt update
sudo apt install php8.1-fpm

On Ubuntu 24.04 and 26.04, you can also run sudo apt install php-fpm to install the default metapackage for that release.

Enable and start the service:

bash
sudo systemctl enable php8.1-fpm
sudo systemctl start php8.1-fpm
sudo systemctl status php8.1-fpm

The status output should show active (running). Verify the default socket exists:

Code sample: ls -l /run/php/php8.1-fpm.sock

On 24.04 the socket path is /run/php/php8.3-fpm.sock. On 26.04 it is /run/php/php8.5-fpm.sock.

Step 2: Create a dedicated PHP-FPM pool

The default pool file lives at /etc/php/8.1/fpm/pool.d/www.conf. For a single site, editing www.conf is fine. For multiple applications, create one pool per site. Separate pools let you assign different users, resource limits, and PHP settings to each app.

This example creates a pool named wordpress_site running under a dedicated user:

Code sample: sudo groupadd wordpress_user
sudo useradd -g wordpress_user -d /var/www/wordpress -s /usr/sbin/nologin wordpress_user

Create the pool configuration file:

Code sample: sudo nano /etc/php/8.1/fpm/pool.d/wordpress_pool.conf

Add the following configuration:

Code sample: [wordpress_site]
user = wordpress_user
group = wordpress_user
listen = /run/php/php8.1-fpm-wordpress.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 500

The pm = dynamic setting starts a base number of workers and scales between min_spare_servers and max_spare_servers based on load. pm.max_children caps total workers to prevent memory exhaustion. pm.max_requests recycles workers after 500 requests to avoid memory leaks.

Reload PHP-FPM to apply changes:

Code sample: sudo systemctl reload php8.1-fpm

Confirm the new socket was created:

Code sample: ls -l /run/php/php8.1-fpm-wordpress.sock

Step 3: Configure NGINX to use the pool

Create or edit a server block in /etc/nginx/sites-available/. The fastcgi_pass directive points to your pool socket:

Code sample: server {
listen 80;
server_name example.com;
root /var/www/wordpress;
index index.php index.html;

location / {
try_files $uri $uri/ /index.php?$args;
}

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm-wordpress.sock;
}

location ~ /\.ht {
deny all;
}
}

Enable the site and test the configuration:

Code sample: sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Step 4: Test the PHP-FPM and NGINX stack

Create a PHP info file in your document root:

Code sample: echo '<?php phpinfo();' | sudo tee /var/www/wordpress/info.php

Open http://example.com/info.php in a browser. You should see the PHP information page listing your PHP version, loaded modules, and server variables. Delete the file after testing. Leaving phpinfo() exposed is a security risk.

Unix socket vs TCP: which should you use?

The fastcgi_pass directive accepts either a Unix socket or a TCP address. Sockets are faster because they skip the network stack. Use them when NGINX and PHP-FPM run on the same machine. TCP (e.g., 127.0.0.1:9000) is necessary when PHP-FPM runs on a separate server or inside a container with network-based communication.

How to fix 502 Bad Gateway errors

A 502 error means NGINX cannot reach PHP-FPM. Check these in order:

  1. Is PHP-FPM running? Run sudo systemctl status php8.1-fpm.
  2. Does the socket exist? Run ls -l /run/php/php8.1-fpm-wordpress.sock.
  3. Do NGINX and the socket have matching permissions? The socket's listen.owner and listen.group should match the user NGINX runs as (usually www-data).
  4. Does the fastcgi_pass path in NGINX match the listen path in the pool config?
  5. Check the PHP-FPM log at /var/log/php8.1-fpm.log and NGINX error log at /var/log/nginx/error.log for specifics.

Most 502s come down to a typo in the socket path or a permission mismatch. When in doubt, reload both services after any config change.

Tuning PHP-FPM for performance

The pm.max_children value is critical. Too low and requests queue. Too high and the server runs out of memory. A rough formula: take your available RAM, subtract memory for NGINX, MySQL, and the OS, then divide by the average memory each PHP worker uses. On a 2GB droplet running WordPress, 10 to 15 children is a reasonable starting point.

Monitor process memory with ps aux --sort=-%mem | grep php-fpm. Adjust pm.max_children based on observed usage under real traffic.

ℹ️

Logicity's Take

PHP-FPM configuration is one of those tasks that looks trivial until it isn't. The pool-per-site pattern deserves wider adoption. It isolates failures, simplifies log analysis, and lets you apply different security restrictions (like disabling exec) to untrusted applications without affecting others. For teams running multiple PHP apps on a single server, separate pools are not optional.

Frequently Asked Questions

Can I run multiple PHP versions with PHP-FPM and NGINX?

Yes. Install each PHP-FPM version (e.g., php8.1-fpm and php8.3-fpm), create separate pools with different sockets, and point each NGINX server block to the appropriate socket.

What is the difference between pm static, dynamic, and ondemand?

Static keeps a fixed number of workers. Dynamic scales between min and max based on load. Ondemand spawns workers only when requests arrive, then terminates them after idle timeout. Dynamic is the default and suits most workloads.

Why use a Unix socket instead of TCP for fastcgi_pass?

Unix sockets are faster because they avoid TCP/IP overhead. Use TCP only when PHP-FPM runs on a different machine or in a network-isolated container.

How do I check if PHP-FPM is processing requests?

Enable the status page by adding pm.status_path = /status to your pool config. Then configure NGINX to serve that path and visit it in a browser or with curl.

Also Read
5 free Obsidian plugins that turn your notes into visual maps

Another practical tools guide for developers managing complex workflows

ℹ️

Need Help Implementing This?

Logicity's team can help you optimize your PHP stack, configure high-availability pools, or troubleshoot persistent 502 errors. Reach out via our contact page for a consultation.

M

Manaal Khan

Tech & Innovation Writer

Related Articles