How to Configure Nginx PHP-FPM

Last updated Monday, May 18, 2026 9:41 am Joshua James 10 min read

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.

PiecePurposeCommon Mistake
rootDefines the filesystem base for the website.Pointing at the project root instead of the public web root.
indexControls which file Nginx serves for directory requests.Leaving out index.php when the app expects PHP as the default entry point.
try_filesChecks static files before routing unmatched paths to PHP.Sending every request to PHP, including missing assets.
fastcgi_passSends PHP requests to a Unix socket or TCP listener.Using a stale PHP-FPM socket path after a PHP version change.
SCRIPT_FILENAMETells 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, or fastcgi_pass line 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 TypeExampleBest Fit
Unix socketunix:/run/php/phpX.Y-fpm.sockSingle-server setups where Nginx and PHP-FPM run on the same host.
Local TCP127.0.0.1:9000Local containers, custom pools, or setups where a socket is not exposed to Nginx.
Private network TCP10.10.10.20:9000Separate 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.php file immediately after testing. Do not leave diagnostic PHP files or phpinfo() 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.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show more of our fresh Linux tutorials in Top Stories and From your sources when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<blockquote>quote</blockquote> quote block

Got a Question or Feedback?

We read and reply to every comment - let us know how we can help or improve this guide.

Let us know you are human: