Nginx and PHP-FPM: how PHP sites run on Nginx
On a site that runs on Apache, PHP mostly works "by itself", because Apache can run PHP inside itself as a module. With Nginx things are different: Nginx cannot run PHP code. Instead it passes the request to PHP-FPM (FastCGI Process Manager), which waits as a separate service to run PHP, takes the response and sends it to the visitor.
The language the two speak to each other is a protocol called FastCGI. So there are two separate programs, two separate configurations and a connection between them. Most of the problems people have with PHP sites on Nginx ("502 Bad Gateway", a blank page, a PHP file being downloaded) come from a setting at one end of this connection. This guide first explains how the arrangement works and then walks through the correct configuration step by step. If you are new to Nginx, have a look at the what is Nginx guide first.
In brief
- Nginx does not run PHP; it passes .php requests to PHP-FPM over FastCGI.
- The fastcgi_pass address must match the listen value in the PHP-FPM pool.
- try_files prevents non-existent files from being sent to PHP.
- If the socket is missing or its permissions are wrong, the visitor sees a 502.
What you need
- Administrator (sudo) rights on the server
- Nginx and the PHP-FPM package installed
- The site's Nginx configuration file and a backup of it
- Access to the Nginx and PHP-FPM logs
Note
The socket and file paths in this guide are examples. The PHP-FPM socket path, service name and configuration folder depend on the distribution and the PHP version; verify the values on your own server as shown in step 2.
On this page
Configuring Nginx and PHP-FPM step by step
-
Understand the flow: who does what?
When a request arrives, the work is divided as follows:
- Static files (images, CSS, JavaScript): Nginx reads them from disk and sends them directly; PHP-FPM is not involved at all.
- PHP requests: Nginx passes the request to PHP-FPM together with the information about which file is to be run. PHP-FPM runs the file with one of its waiting worker processes and hands the output back to Nginx.
Nginx and PHP-FPM talk to each other in one of two ways:
- Unix socket: A special file on disk (e.g.
/run/php/php-fpm.sock). Only processes on the same machine can use it; access is controlled by file permissions. - TCP address: An address and port such as
127.0.0.1:9000. This is needed if PHP-FPM runs on another machine or in a container.
If the two are on the same machine, a Unix socket is the common choice; whichever you pick, the same value must be written on both sides.
: Enlarge -
Check that PHP-FPM is running and find where it listens
First make sure the PHP-FPM service is running, then find the
listenline in the pool file. This line shows the socket or address that Nginx will connect to.Bash# Is PHP-FPM running? (the service name depends on the distribution) systemctl status php-fpm # RHEL, AlmaLinux and similar systemctl status 'php*-fpm' # Debian, Ubuntu: the name contains the version # Which socket or address does the pool listen on? sudo grep -R "^listen" /etc/php-fpm.d/ /etc/php/ 2>/dev/null # Does the socket file exist, and what are its owner and permissions? ls -l /run/php/ /run/php-fpm/ 2>/dev/nullThe service name and the paths depend on the distribution: on Debian and Ubuntu the service name and the paths contain the PHP version (e.g. a socket with a version number under
/run/php/, pool files under/etc/php/<version>/fpm/pool.d/); on RHEL, AlmaLinux and similar systems the service is mostlyphp-fpm, the pool folder is/etc/php-fpm.d/and the socket is under/run/php-fpm/. Hosting control panels may use their own paths. Do not guess; use whatever thelistenline says. -
Route PHP requests to PHP-FPM in the server block
The
serverblock below shows the basic layout for a site that runs on PHP.Nginxserver { listen 80; server_name example.com www.example.com; root /var/www/example.com/public; index index.php index.html; location / { try_files $uri $uri/ =404; } location ~ \.php$ { # Do not send the request to PHP if the file is not on disk try_files $uri =404; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # The socket path depends on the distribution; it must match "listen" in the pool fastcgi_pass unix:/run/php/php-fpm.sock; # For a pool that listens over TCP: fastcgi_pass 127.0.0.1:9000; } }What it means, line by line:
index index.php index.html;Says which file to look for when a folder is requested (e.g./). Ifindex.phpis not in the list, the home page does not open or a 403 is returned.try_filesinsidelocation /: If the requested address is a real file or folder it is served; otherwise a 404 is returned.location ~ \.php$: Catches requests whose address ends in.php.fastcgi_pass: The PHP-FPM address the request is passed to. For a Unix socket it is written as a path with theunix:prefix, for TCP in the form127.0.0.1:9000.
In applications that handle every request in a single
index.phpfile (most frameworks and off-the-shelf content management systems), thelocation /block is slightly different: if no file is found, the request is routed toindex.phpinstead of returning a 404.Nginx# Inside the "location /" block: if there is no file or folder, hand the request to index.php try_files $uri $uri/ /index.php?$query_string;Without this block the home page opens but the inner pages return 404. In Apache the same job is done by the rewrite rules in
.htaccess; for moving them, see the guide on migrating from Apache to Nginx.
: Enlarge -
Supply fastcgi_params and SCRIPT_FILENAME correctly
Nginx does not merely tell PHP-FPM "there is a request"; it sends the details of the request as FastCGI parameters: the request method, the query string, the visitor's IP address, the server name and so on. On the PHP side these appear in the
$_SERVERarray.include fastcgi_params;Pulls in the file that ships with Nginx and defines these standard parameters.SCRIPT_FILENAMEis the parameter that tells PHP-FPM, with its full path, which file to run. The value$document_root$fastcgi_script_namejoins the folder you gave withrootand the name of the requested file.
The
fastcgi_paramsfile usually does not contain theSCRIPT_FILENAMEline; that is why it is written separately in the example. The file calledfastcgi.conf, which also ships with Nginx, does contain this line; if you use that one you do not need to write the line a second time. The Debian and Ubuntu packages also include a ready-made snippet file that bundles these settings. Whichever you use, check that the parameter is defined once and correctly.If
SCRIPT_FILENAMEis missing or wrong, PHP-FPM cannot find the file: the visitor sees a blank page or the response "File not found.". The most common cause is arootdirective that points to the wrong folder, or a differentrootdefined inside thelocation. -
Do not send non-existent files to PHP
The first line of the PHP block in the example,
try_files $uri =404;, is a defensive measure. If the requested.phpfile does not exist on disk, Nginx does not pass the request to PHP-FPM at all and returns a 404 straight away.Why does this matter? Without the check, a
.phpaddress that does not really exist reaches PHP and, depending on PHP's path resolution setting, a different file may be run as PHP. If your site has an area where users can upload files, this can go as far as an uploaded file being executed as code. A one-line check closes this door.- This check works when Nginx and PHP-FPM see the same file system. If PHP-FPM is on a separate machine or in a container, Nginx cannot see the file and every request becomes a 404; in that arrangement the check has to be done on the PHP side.
- As an additional layer, the
security.limit_extensionssetting in the PHP-FPM pool limits which extensions may be run as PHP; do not loosen this limit.
-
Get to know the pool settings
PHP-FPM manages its worker processes in groups called pools. Each pool has its own listen address, its own user and its own process limits. If there are several sites on the same server, defining a separate pool and a separate user for each makes it harder for a problem in one site to reach the files of another.
PHP-FPM; Pool file (e.g. www.conf); its path depends on the distribution [example] user = example group = example ; Must match fastcgi_pass in Nginx listen = /run/php/php-fpm.sock ; Owner and permissions of the socket: the Nginx user must have access ; (Debian, Ubuntu: www-data; RHEL, AlmaLinux: nginx) listen.owner = www-data listen.group = www-data listen.mode = 0660 ; Process management (the numbers are a syntax example, not a recommendation) pm = dynamic pm.max_children = 10 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3Setting What does it determine? listenThe socket or address PHP-FPM listens on. It must match fastcgi_passin Nginx.user,groupWhich user the PHP code runs as. File read and write permissions apply according to this user. pmThe process management mode: static(a fixed number of processes),dynamic(between a lower and an upper limit depending on load) orondemand(started as requests arrive, stopped when idle).pm.max_childrenThe upper limit on the number of worker processes that can run at the same time; in other words, the number of PHP requests that can be processed simultaneously. There is no one-size-fits-all number for
pm.max_children. Every worker process uses memory; if the limit is too high the server runs out of memory at busy times, and if it is too low requests wait in a queue and a warning that the limit has been reached appears in the PHP-FPM log. The right value is found by measuring, looking at the server's free memory and at the memory your application uses per process. Read the numbers in the example as a syntax example, not as a recommendation.
: Enlarge -
Check socket permissions and the 502 error
"502 Bad Gateway" means that Nginx could not get a valid response from the service behind it. On PHP sites the most common cause is that Nginx cannot connect to PHP-FPM. Do not guess the reason; the Nginx error log states it plainly.
Phrase in the log Likely cause Where to look No such file or directory The socket file does not exist: PHP-FPM is not running or the fastcgi_passpath is wrong (e.g. PHP was upgraded and the socket name changed)Service status; do listenandfastcgi_passmatch?Permission denied The socket exists, but the user Nginx runs as has no permission to access it listen.owner,listen.group,listen.modeConnection refused No service is listening on the TCP address Service status; address and port Because a Unix socket is a file, it has permissions. In the pool,
listen.ownerandlisten.groupset the owner of the socket andlisten.modesets its permissions. The user the Nginx worker processes run as (depending on the distribution, mostlywww-dataornginx) must be able to access this socket with read and write permission. Opening the socket to everyone (0666) "solves" the problem but allows every user on the server to send requests to PHP-FPM; set the owner and group correctly instead.If the page gives an error after waiting for a while (504), or if the 502 only appears at busy times, the cause is not the connection but slowness on the PHP side or the process limit. For detailed diagnosis, see the guide on 502 and 504 errors.
: Enlarge -
Turn off PHP execution in the upload folder
There is no reason for PHP to run in the folder where visitors or administrators upload files (e.g.
/upload/). Even if there is a flaw in your upload checks, a malicious uploaded file cannot be executed if files in that folder are not sent to PHP-FPM. This is a second line of defence that is not sufficient on its own but is effective.Nginx# Must be written BEFORE the general "location ~ \.php$" block location ~* ^/upload/.*\.(php|phtml|phar)$ { return 403; }Pay attention to two points: change the folder name to suit your own site, and write this block before the general
location ~ \.php$block. Nginx trieslocationblocks with regular expressions in the order they appear in the file and uses the first match; if the order is reversed the rule does not take effect. For upload security as a whole, see the guide on file upload security.
: Enlarge -
Test, reload, verify
After your changes, check the configuration first and then reload the relevant service. If you changed the Nginx file you need to reload Nginx; if you changed the pool file you need to reload PHP-FPM.
Bash# Check the Nginx configuration and reload sudo nginx -t sudo systemctl reload nginx # If the pool file changed, reload PHP-FPM (the service name depends on the distribution) sudo systemctl reload php-fpm # If something is wrong, look at the latest error entries sudo tail -n 50 /var/log/nginx/error.logThen test from the browser: does the home page open, does an inner page open, does a non-existent
.phpaddress return 404, does a.phpaddress in the upload folder return 403? If a PHP file is downloaded instead of being run, or its source code appears on screen, the request is not reaching the PHP block at all: check that thelocation ~ \.php$block is inside the rightserverand that a reload has been done.
Common symptoms and their causes
| Symptom | Likely cause |
|---|---|
| 502 Bad Gateway | PHP-FPM is not running, the socket path is wrong or the socket permissions do not give Nginx access |
| Blank page or "File not found." | SCRIPT_FILENAME is missing or wrong; root points to the wrong folder |
| The PHP file is downloaded | There is no location block for PHP, or the request falls into another block |
| Home page opens, inner pages return 404 | The try_files fallback to the front controller (index.php) is missing |
| 403 on the home page | index.php is not in the index directive, or the folder permissions do not allow Nginx to read it |
| 413 when uploading a file | Nginx's request body limit (client_max_body_size, default 1m) is exceeded; PHP's own upload limits also apply separately |
In every case the first place to look is the logs: the Nginx error log (mostly /var/log/nginx/error.log) and the PHP-FPM log. Their locations may vary with the configuration.
A short security checklist
- The PHP block contains
try_files $uri =404;; non-existent files do not go to PHP. - PHP execution is turned off in upload folders.
- Socket permissions are tight: only the Nginx user or group has access.
- If PHP-FPM listens over TCP, it listens only on
127.0.0.1or an internal network address; it is not open to the internet. - Each site runs in its own pool and as its own user; PHP processes do not run as the administrator (root).
- The PHP user can write only to the folders it needs.
- Error details are written to the log, not shown to the visitor.
For the server as a whole, see the guides on Nginx security settings and server and hosting security.
Frequently asked questions
On Nginx my PHP file is downloaded instead of being run. Why?
It means the request is not being passed to PHP-FPM. The site's server block must contain a location block that catches the .php extension, with fastcgi_pass inside it. If the block is there, check that it is inside the right server, that the configuration has been tested and reloaded, and that the browser is not showing an old response from its cache.
Should I use a Unix socket or 127.0.0.1:9000?
If Nginx and PHP-FPM are on the same machine, a Unix socket is the common choice; it does not open a network port and access is controlled by file permissions. If PHP-FPM runs on a separate machine or in a container, TCP is required. Whichever you choose, fastcgi_pass and listen in the pool must match.
I upgraded PHP and the site started returning 502. Why?
On some distributions the names of the socket file and the service contain the PHP version. When the version changes the socket path changes too, while Nginx keeps trying to connect to the old path. Find the listen value in the new pool file, update the fastcgi_pass line accordingly, then test and reload.
What should pm.max_children be?
There is no value that suits everyone. It depends on the memory available on the server and on the memory your application uses per process. Too high a value exhausts memory, too low a value keeps requests waiting. Set it by measuring on your own server and by watching the warnings in the PHP-FPM log.
Do my Apache .htaccess rules work with PHP-FPM?
No. .htaccess is specific to Apache; neither Nginx nor PHP-FPM reads this file. Redirect, access restriction and URL rewriting rules have to be moved into the Nginx configuration.
BYK Yazılım Support Team
This guide is written and regularly reviewed by the BYK Yazılım support team. Last updated: 4 October 2026.
Related guides
- What is Nginx and what is it used for?The roles of Nginx, its event-driven architecture, how it differs from Apache, configuration blocks and basic commands.
- 502, 504 and other Nginx errors: causes and fixesWhat 502, 504, 413, 499, 403 and 404 mean, their likely causes, diagnosis from the logs and the fix.
- Migrating from Apache and .htaccess to NginxSide-by-side examples, an equivalents table and a test list for moving .htaccess rules into the Nginx configuration.
- File upload security and web shellsSecure upload rules, disabling execution in the upload folder, signs of a web shell and what to do if you find one.
Let us talk about your website infrastructure
BYK Yazılım builds corporate websites. Write to us with any questions about your site.
Contact us Our corporate website service