Nginx does not execute PHP by itself. It serves static files directly and sends PHP requests to PHP-FPM through FastCGI. When that handoff is wrong, the symptoms are usually 502 errors, missing PHP pages, downloaded PHP source code, or front-controller routes that never reach the application.
A reliable setup needs checks for normal PHP sites, front-controller apps such as WordPress or Laravel, and both socket and TCP PHP-FPM listeners. The key is to verify the active listener, avoid common SCRIPT_FILENAME mistakes, and troubleshoot the Nginx and PHP-FPM layers separately.
Understand Nginx PHP-FPM Request Flow
A working Nginx PHP-FPM setup has three parts: a document root that points to the public PHP application directory, a location block that decides which requests should reach PHP, and a FastCGI handoff that tells PHP-FPM the exact script file to run.
| Piece | Purpose | Common Mistake |
|---|---|---|
root | Defines the filesystem base for the website. | Pointing at the project root instead of the public web root. |
index | Controls which file Nginx serves for directory requests. | Leaving out index.php when the app expects PHP as the default entry point. |
try_files | Checks static files before routing unmatched paths to PHP. | Sending every request to PHP, including missing assets. |
fastcgi_pass | Sends PHP requests to a Unix socket or TCP listener. | Using a stale PHP-FPM socket path after a PHP version change. |
SCRIPT_FILENAME | Tells PHP-FPM which PHP file to execute. | Building the wrong path when root, alias, or the app public directory is wrong. |
The important boundary is simple: Nginx owns HTTP routing, static files, headers, and FastCGI parameters. PHP-FPM owns PHP execution. A successful Nginx syntax test proves the configuration parses, but it does not prove that PHP-FPM is running, that the socket path is current, or that the application can execute the requested script.
Apply PHP-FPM changes during a maintenance window when the site already serves production traffic. A wrong
root,location, orfastcgi_passline can affect every PHP request for the virtual host.
Choose a Nginx PHP-FPM Socket or TCP Listener
PHP-FPM usually listens through a Unix socket or a local TCP port. A Unix socket is common when Nginx and PHP-FPM run on the same server. TCP is useful when PHP-FPM runs in a container, another host, or a separately managed service.
| Listener Type | Example | Best Fit |
|---|---|---|
| Unix socket | unix:/run/php/phpX.Y-fpm.sock | Single-server setups where Nginx and PHP-FPM run on the same host. |
| Local TCP | 127.0.0.1:9000 | Local containers, custom pools, or setups where a socket is not exposed to Nginx. |
| Private network TCP | 10.10.10.20:9000 | Separate app hosts or containers behind private networking. |
Do not expose PHP-FPM directly to the public internet. Keep TCP listeners bound to loopback or a private network, then control public access through Nginx.
When a snippet uses phpX.Y, treat it as a placeholder for the PHP branch your package installed. Debian-family systems often use versioned paths such as /run/php/phpX.Y-fpm.sock, while Fedora and RHEL-family packages commonly use /run/php-fpm/www.sock. Copy the listener from your own system instead of reusing a version from another distribution or release.
Find the PHP-FPM Listener for Nginx
Find the PHP-FPM socket before writing the Nginx server block. Package layouts vary by distribution and PHP version, so guessing the socket path is one of the easiest ways to create a 502 error.
sudo find /run /var/run -type s \( -name 'php*-fpm.sock' -o -name 'www.sock' \) -print 2>/dev/null
Common socket patterns include these paths:
/run/php/phpX.Y-fpm.sock /run/php-fpm/www.sock
If PHP-FPM listens over TCP, check for a local listener instead:
ss -ltn | grep ':9000'
Use the socket or address that your PHP-FPM pool actually exposes. If no socket or TCP listener appears, start with the PHP-FPM service before editing Nginx.
systemctl list-unit-files | grep -E '^php.*fpm|^php-fpm'
Service names vary by package family. Debian and Ubuntu commonly use versioned units such as phpX.Y-fpm.service, while Fedora, RHEL, and similar systems commonly use php-fpm.service. Use the unit name your system lists.
Configure Nginx with PHP-FPM over a Unix Socket
Use a Unix socket when PHP-FPM runs on the same host as Nginx. The example uses a generic PHP application rooted at /var/www/example.com/public. Replace the domain, root path, and phpX.Y socket placeholder with your real values.
Choose the Nginx PHP-FPM Site File
Debian and Ubuntu packages commonly use /etc/nginx/sites-available/ with symlinks in /etc/nginx/sites-enabled/. Fedora, RHEL-family, nginx.org packages, and many custom layouts commonly load site files from /etc/nginx/conf.d/.
If you are modifying an existing virtual host instead of creating a new one, copy the current file first so the rollback step has a known-good configuration to restore.
On Debian and Ubuntu, create a site file:
sudo nano /etc/nginx/sites-available/php-fpm-site
On systems that load conf.d files, create a .conf file instead:
sudo nano /etc/nginx/conf.d/php-fpm-site.conf
If you are unsure which include layout your Nginx package uses, inspect the loaded include lines:
sudo nginx -T 2>/dev/null | grep -E 'include .*(sites-enabled|conf\.d)'
Relevant lines look like this on common packaged layouts:
include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*;
Add the Nginx PHP-FPM Server Block
Add this server block and change example.com, the document root, and the socket path. The PHP location includes try_files $uri =404; so Nginx does not pass nonexistent PHP paths to PHP-FPM.
server {
listen 80;
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/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/run/php/phpX.Y-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
The location / block serves real static files first, then routes unmatched application paths to /index.php. The PHP location executes only PHP files that actually exist under the document root. For a deeper explanation of that fallback, use the Nginx try_files directive guide.
The example uses include /etc/nginx/fastcgi_params; and then sets SCRIPT_FILENAME explicitly. Some packages also provide /etc/nginx/fastcgi.conf, which may already define SCRIPT_FILENAME. Use one clear pattern and verify the generated configuration with nginx -T if you change the include file. Nginx documents FastCGI directives in the official module reference when you need to check directive context or defaults.
On Debian and Ubuntu, enable the site after saving it:
sudo ln -s /etc/nginx/sites-available/php-fpm-site /etc/nginx/sites-enabled/
Systems using /etc/nginx/conf.d/*.conf do not need a symlink step.
Configure Nginx with PHP-FPM over TCP
Use a TCP listener when PHP-FPM is exposed on loopback, a private container network, or another private host. Keep the rest of the Nginx server block the same, but replace the socket target with the PHP-FPM address.
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}
For a PHP-FPM service running on another private host, keep the same PHP location shape and change only the fastcgi_pass target:
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass 10.10.10.20:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}
When PHP-FPM runs outside the Nginx host, test private network reachability before changing the site. A firewall, container network, or cloud security group can block the upstream even when the Nginx syntax is correct. If nc is not installed, use your distribution package manager to install a netcat package or use another TCP reachability check you already trust.
nc -vz 10.10.10.20 9000
A reachable TCP listener reports a successful connection. If the check fails, fix the PHP-FPM bind address, host firewall, container port, or network route before reloading Nginx.
Use Nginx PHP-FPM for Front Controller Applications
WordPress, Laravel, Symfony, and many custom PHP apps use a front controller. Static files should be served directly, while unknown application routes should reach index.php. That is why try_files $uri $uri/ /index.php?$query_string; belongs in the main location block for many PHP apps.
location / {
try_files $uri $uri/ /index.php?$query_string;
}
Keep the PHP execution location separate from the general routing location:
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/run/php/phpX.Y-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}
This split prevents a missing PHP file from being blindly handed to PHP-FPM, while still allowing clean URLs such as /posts/example to reach the application entry point.
Handle PHP PATH_INFO Only When the App Needs It
Some older PHP applications expect URLs such as /index.php/profile/123 and read the extra path from PATH_INFO. Do not add this pattern unless the application documentation requires it, because it changes how PHP paths are parsed.
location ~ ^(.+\.php)(/.+)$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
try_files $fastcgi_script_name =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/run/php/phpX.Y-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
Most modern PHP front-controller applications do not need this extra location. If you use it, test both normal PHP files and PATH_INFO URLs before pushing the change to production.
Test and Reload Nginx PHP-FPM Configuration
Test the Nginx configuration before any reload:
sudo nginx -t
A successful syntax test prints these lines:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Reload Nginx only after the syntax test passes:
sudo systemctl reload nginx
The reload command normally returns no output on success. Use a local request with the expected host header to verify that Nginx is serving the right virtual host:
curl -I -H "Host: example.com" http://127.0.0.1/
Relevant headers should come from the expected virtual host, not the default Nginx page or another site. A working test page can look like this:
HTTP/1.1 200 OK Server: nginx
Create a Temporary PHP-FPM Test File
When the application is not ready yet, create a short temporary PHP file in the configured document root. This proves that Nginx can hand a PHP request to PHP-FPM.
printf '%s\n' '<?php echo "php-fpm-ok\n";' | sudo tee /var/www/example.com/public/fpm-test.php > /dev/null
Request the test file through Nginx:
curl -H "Host: example.com" http://127.0.0.1/fpm-test.php
A working Nginx PHP-FPM handoff prints this output:
php-fpm-ok
Remove the temporary
fpm-test.phpfile immediately after testing. Do not leave diagnostic PHP files orphpinfo()pages exposed on a public site.
sudo rm -f /var/www/example.com/public/fpm-test.php
Secure Nginx PHP-FPM Sites
PHP-FPM configuration affects both reliability and security. The safest baseline is to execute only existing PHP files, keep private files outside the document root, and block obvious sensitive paths from public requests.
Block Hidden Files in Nginx PHP-FPM Sites
Hidden files such as .env, .git, and application dotfiles should not be served by Nginx. The example keeps .well-known available so ACME challenge paths can still work when your certificate workflow uses them.
location ~ /\.(?!well-known).* {
deny all;
}
For Laravel, Symfony, and similar apps, point root to the application public directory, not the full project directory. That keeps configuration files, vendor code, storage directories, and environment files outside the web root.
Stop PHP Execution in Upload Directories
If users can upload files, block PHP execution inside upload paths unless the application explicitly requires a different layout.
location ~* ^/(uploads|files)/.*\.php$ {
return 403;
}
Place this rule before the general PHP location so upload-path PHP files are denied before they reach PHP-FPM. If your site uses a different upload directory, adjust the path names and test a blocked URL with curl -I.
Keep PHP-FPM Private from Public Traffic
When PHP-FPM listens on TCP, bind it to 127.0.0.1 or a private network address. Opening port 9000 to the internet bypasses the web server layer and can expose unsafe FastCGI behavior.
ss -ltn | grep ':9000'
If the listener shows a public bind address, fix the PHP-FPM pool configuration before relying on Nginx as the only access-control layer.
Troubleshoot Nginx PHP-FPM Errors
Troubleshoot PHP-FPM problems by separating the Nginx layer from the PHP runtime layer. Start with the Nginx error log, then verify the FastCGI listener, script path, file permissions, and application logs.
sudo tail -n 50 /var/log/nginx/error.log
If your site uses per-server logs, inspect the site-specific error log instead. The Nginx access and error logs guide explains how to find active log paths from the loaded configuration.
Fix Nginx PHP-FPM 502 Bad Gateway Errors
A 502 usually means Nginx matched the PHP location but could not communicate with PHP-FPM. The common causes are a stopped PHP-FPM service, a stale socket path, a permission problem on the socket, or a TCP listener that Nginx cannot reach.
connect() to unix:/run/php/phpX.Y-fpm.sock failed (2: No such file or directory) while connecting to upstream
Recheck the active socket path and update fastcgi_pass if the package changed PHP branches:
sudo find /run /var/run -type s \( -name 'php*-fpm.sock' -o -name 'www.sock' \) -print 2>/dev/null
If the socket exists but the error mentions permission denied, inspect the Nginx worker user and the PHP-FPM pool socket settings:
ps -o user,group,comm -C nginx
sudo grep -R -E '^(user|group|listen|listen.owner|listen.group|listen.mode|listen.acl_users)[[:space:]]*=' /etc/php/*/fpm/pool.d/*.conf /etc/php-fpm.d/*.conf 2>/dev/null
The fix is package-layout dependent: either use the socket group, mode, or ACL created by the PHP-FPM package, or adjust the PHP-FPM pool so the Nginx worker user can connect. After a pool change, use the PHP-FPM package’s config test or service reload path, then rerun sudo nginx -t and the local PHP request. For a full symptom map, use the Nginx 502 Bad Gateway troubleshooting guide.
Fix Nginx PHP-FPM 404 Errors
A PHP request can return 404 when root points at the wrong directory, SCRIPT_FILENAME builds the wrong filesystem path, or try_files $uri =404; correctly refuses to pass a missing PHP file to PHP-FPM.
sudo nginx -T 2>/dev/null | grep -E 'root |SCRIPT_FILENAME|fastcgi_pass|try_files'
Compare the generated SCRIPT_FILENAME path with the actual file location. If the app lives under a public directory, the Nginx root should usually point to that public directory. Use the Nginx root vs alias guide if path mapping is unclear, use the Nginx try_files guide when front-controller routing is the part that fails, and use the Nginx 404 troubleshooting guide when the missing path is outside the PHP-FPM handoff.
Fix PHP Source Downloading Instead of Executing
If the browser downloads PHP source code, Nginx is serving the file as static content instead of sending it to PHP-FPM. That usually means the PHP location is missing, not loaded, or losing to another location block.
sudo nginx -T 2>/dev/null | grep -E 'location .*\\.php|fastcgi_pass'
Confirm that the server block handling the hostname contains the PHP location and that no more specific location serves the file first. If the location match is confusing, compare the request against the Nginx location block priority guide.
Fix Nginx PHP-FPM 504 Gateway Timeout Errors
A 504 usually means PHP-FPM accepted the request but did not return a response before Nginx or another proxy timed out. Do not raise every timeout first. Check the PHP-FPM pool, slow logs, application logs, database calls, and external API calls.
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/run/php/phpX.Y-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_read_timeout 120s;
}
Use a targeted timeout increase only after identifying a legitimate long-running PHP request. Timeout ownership can sit in Nginx, PHP-FPM, the application, a database, an external API, a CDN, or a load balancer, so keep the change tied to the slow request you identified.
Fix Missing FastCGI Include Files
If nginx -t reports that /etc/nginx/fastcgi_params does not exist, your package or source build uses a different configuration prefix. Find the available FastCGI include files:
sudo find /etc/nginx -maxdepth 2 -type f \( -name 'fastcgi_params' -o -name 'fastcgi.conf' \) -print
Use the file your Nginx package provides, then run sudo nginx -t again. If you switch from fastcgi_params to fastcgi.conf, inspect the loaded configuration so SCRIPT_FILENAME is not defined twice with conflicting values.
Roll Back Nginx PHP-FPM Configuration
If the new PHP-FPM site breaks traffic, disable the new site file and reload the last working configuration. Use the path that matches your package layout.
On Debian and Ubuntu, remove the enabled symlink:
sudo rm -f /etc/nginx/sites-enabled/php-fpm-site
On conf.d layouts, move the file out of the active include path:
sudo mv /etc/nginx/conf.d/php-fpm-site.conf /etc/nginx/conf.d/php-fpm-site.conf.disabled
Test and reload after the rollback:
sudo nginx -t && sudo systemctl reload nginx
If the rollback restores service, compare the disabled file against the working server block before trying again. The most common differences are the wrong document root, wrong socket path, duplicate server names, or a missing PHP location.
Conclusion
Nginx is paired with PHP-FPM when the virtual host, document root, PHP location, FastCGI listener, and SCRIPT_FILENAME path all point at the same application. Keep nginx -t, a local PHP test request, and the error log near each change; use the Nginx try_files guide, Nginx root vs alias guide, and Nginx log guide when routing or file paths still fail.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>