“Unable to open primary script” means PHP-FPM received a filesystem path that it cannot open. In most cases, Nginx built the wrong SCRIPT_FILENAME from the request URI and its root/alias; in other cases, the path is correct on the host but invisible or unreadable to the PHP-FPM worker. Read the error suffix, calculate the exact path, then test that path from the pool’s filesystem and security context.
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
What PHP-FPM is trying to open
Nginx sends PHP-FPM a FastCGI parameter named SCRIPT_FILENAME. PHP uses that parameter as the script’s absolute filename. A common configuration constructs it as:
As an Amazon Associate I earn from qualifying purchases.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems$document_root$fastcgi_script_name
$document_root is the value supplied by the active Nginx root directive. $fastcgi_script_name is derived from the request URI. For a request to /index.php under root /var/www/site;, the resulting filename should be /var/www/site/index.php.
If the resulting path is wrong, missing, outside a chroot, or blocked by permissions, PHP-FPM reports the failure. The path printed after Unable to open primary script: is therefore the most useful clue; it is the path PHP-FPM actually received, not merely the URL the browser requested.
#1 Best Overall
Read the error suffix before changing configuration
| Error ending | What it usually means | First checks |
|---|---|---|
No such file or directory |
The generated path does not exist in the worker’s filesystem view. | Active root/alias, duplicate fastcgi_param includes, URI-to-file mapping, fastcgi_index, and chroot paths. |
Permission denied |
The path exists, but the FPM worker cannot traverse a directory, read the script, or pass a mandatory access-control policy. | Pool identity, directory execute bits, file read bits, ACLs, SELinux/AppArmor, and the exact path shown in the log. |
Do not treat both messages as the same problem. Adding a file when the suffix is Permission denied will not help, and changing permissions will not repair a path that contains an extra directory prefix.
A diagnostic sequence that finds the real cause
-
Capture the complete request and filename
Copy the request URI, the complete Nginx error-log line, and its final operating-system message. Record whether the request was for a plain script such as
/index.php, a directory such as/, or a path containing additional information such as/index.php/foo. The difference determines how$fastcgi_script_nameis formed.Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Inspect the location Nginx actually selected
Review the server block and every included file. Pay particular attention to regex PHP locations, nested locations,
alias, and snippets that contain FastCGI parameters. Runnginx -Tand inspect the complete expanded configuration, not just the file you recently edited. A location-levelfastcgi_paramchanges the effective value, and Nginx inherits FastCGI parameters only when none are defined at the current level. Two parameter includes can therefore leave a differentSCRIPT_FILENAMEthan the one you intended. -
Calculate the filename by hand
Write down the active document root and the exact
$fastcgi_script_namefor the request, then concatenate them. For/index.phpwith/var/www/site, the expected result is/var/www/site/index.php. If the log shows/var/www/site/var/www/site/index.php, a host path has been prefixed twice. If it shows a directory or an unexpected application subdirectory, the selected location or alias is wrong. -
Test the path from PHP-FPM’s view
Confirm that the exact filename exists where the worker can see it. A shell check such as
ls -l /var/www/site/index.phpreflects the host namespace. It does not prove that a pool withchrootenabled sees that same path. Compare the host path with the path inside the jail, and account for containers or other filesystem namespaces as well. -
Check the pool identity and access policy
Read the pool configuration for
userandgroup. That identity must be able to execute every parent directory and read the PHP file. If the suffix isPermission denied, also check ACLs and mandatory controls such as SELinux or AppArmor; a normal Unix mode listing can look correct while policy still denies the open.What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Reject missing scripts before FastCGI dispatch
Add a file-existence check such as
try_files $uri =404;to the PHP location, or an equivalent-ftest. A nonexistent script should become a normal 404 rather than being sent to PHP-FPM. After changing routing, retest both an existing script and a deliberately missing one.
Build and verify SCRIPT_FILENAME correctly
Standard document-root layout without chroot
For a conventional site whose Nginx and PHP-FPM processes share the host filesystem, use one clear mapping:
location ~ .php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
The socket path is distribution- and pool-specific; use the socket configured by your PHP-FPM service. The important part is that the prefix in SCRIPT_FILENAME resolves to the same absolute path that the FPM worker can open.
A hard-coded path can be equally valid when the virtual host has one fixed deployment directory:
Recommended Free Tools
fastcgi_param SCRIPT_FILENAME /var/www/site$fastcgi_script_name;
Do not combine a hard-coded directory with a $document_root that already contains that directory. That creates the duplicated-prefix failure visible in the log.
Why root and alias are not interchangeable
With root, Nginx appends the request URI to the root directory. With alias, Nginx replaces the matching location prefix with the alias path. A PHP location designed around $document_root$fastcgi_script_name can therefore point to the wrong place when the request is served from an aliased location.
For an aliased application, calculate the replacement explicitly and use a path that matches the alias’s file layout. Keep the PHP handler inside the correct location, and verify the resulting filename in the error log or with a controlled test request. Do not assume that changing root to alias leaves the FastCGI expression unchanged.
Directory requests and fastcgi_index
When a URI ends in /, Nginx can append the filename configured by fastcgi_index while forming $fastcgi_script_name. A request for / may therefore become /index.php, while a request for another directory may produce a different index path. Make sure the configured index file exists under the active root and that the application’s front controller is actually named that way.
Chroot: the most common “file exists” contradiction
PHP-FPM’s chroot changes the root directory seen by workers at startup. If a pool has chroot = /var/www/site, the host file /var/www/site/index.php is visible to that worker as /index.php. Passing the host path to PHP-FPM makes it search for /var/www/site/var/www/site/index.php inside the jail, which does not exist.
| Host filesystem | Path inside chroot = /var/www/site |
Value PHP-FPM should receive |
|---|---|---|
/var/www/site/index.php |
/index.php |
/index.php |
/var/www/site/public/index.php |
/public/index.php |
/public/index.php |
In a chrooted pool, pass the path that exists inside the jail, often the chroot-relative $fastcgi_script_name, rather than prepending the host directory. The exact expression depends on your Nginx root and application layout; derive it from the worker’s view instead of copying a no-chroot example.
Chroot affects more than the script. The pool’s socket, logs, temporary directories, session storage, working directory, and any files loaded by the application must be reachable at paths valid inside the jail. A corrected script path can expose the next missing in-jail directory if those supporting paths were left as host-absolute locations.
Fixing Permission denied
Check every directory in the path
To open /var/www/site/public/index.php, the FPM identity needs directory execute (x) permission on /var, /var/www, /var/www/site, and /var/www/site/public, plus read permission on the file. A readable file is still inaccessible when one parent directory blocks traversal.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #4
Use namei -l /var/www/site/public/index.php where available to inspect each component. Then verify the pool identity with a targeted access test, for example:
sudo -u www-data test -r /var/www/site/public/index.php && echo readable
sudo -u www-data test -x /var/www/site/public && echo traversable
Replace www-data with the actual user configured for the pool. Do not solve the problem by making the entire tree world-writable; grant the minimum traversal and read access required by the deployment.
Check ACLs and mandatory access control
POSIX ACLs can override what a basic mode listing suggests, so inspect them with getfacl when permissions look correct but access still fails. On systems enforcing SELinux, inspect audit events and apply a policy-compatible file context rather than disabling enforcement. On AppArmor systems, review the profile denial for the PHP-FPM process and allow only the required application paths. The exact error suffix and the security log together distinguish an operating-system permission problem from a policy denial.
Handle PATH_INFO and front-controller routing deliberately
A location matching every URI that ends in .php does not automatically handle /index.php/something correctly. If your application uses PATH_INFO, configure path splitting intentionally, ensure the script portion is the file you expect, and apply try_files to that script rather than blindly accepting arbitrary suffixes. If the application does not use PATH_INFO, reject or reroute those requests instead of allowing an accidental filename calculation.
Test these cases separately:
/index.php— the direct script./— index handling andfastcgi_index./missing.php— should return 404 before PHP-FPM./index.php/extra— only if PATH_INFO is a documented application feature.
Use a controlled reload and verification cycle
-
Save the current Nginx server block and PHP-FPM pool configuration so you can revert a failed change.
-
Run
nginx -t. Do not reload if the syntax or included-file test fails. -
Reload Nginx and restart or reload the affected PHP-FPM service when its pool configuration changed. Use the service name installed on your system.
-
Request an existing PHP file and inspect both the HTTP response and the Nginx/PHP-FPM logs. A successful response alone is not sufficient if the request was served from a different location than the failing one.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Request a known missing PHP filename and confirm that Nginx returns 404 without an FPM “Unable to open primary script” entry.
-
Repeat the test for directory, aliased, PATH_INFO, and chrooted routes used by the application.
Only declare the issue fixed after the exact failing URL works and the error log no longer records the original path failure.
Fast reference: choose the correction by symptom
| Observed symptom | Likely correction |
|---|---|
| Log path contains the site root twice | Remove the duplicate prefix; use either $document_root$fastcgi_script_name or one hard-coded absolute prefix, not both. |
| Log path points to the wrong virtual host directory | Inspect the winning location, server name, regex precedence, and included fastcgi_param definitions. |
| File exists on host but not for a chrooted pool | Use the chroot-relative path and make socket, logs, temporary files, sessions, and working directory valid inside the jail. |
Existing file returns Permission denied |
Check pool user/group, parent-directory execute permission, file read permission, ACLs, and SELinux/AppArmor. |
| Missing scripts reach PHP-FPM | Add try_files $uri =404; or an equivalent file test before fastcgi_pass. |
| Only directory or PATH_INFO URLs fail | Check fastcgi_index, path splitting, and whether the generated script name matches the application layout. |
Keep the FastCGI endpoint protected
Use a restricted local Unix socket or a protected local endpoint for PHP-FPM. A client that can open a FastCGI connection can influence request configuration, including auto_prepend_file, and may be able to execute arbitrary code. Do not expose the FPM socket or port to untrusted networks while troubleshooting the script path.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteThe durable fix is the simplest mapping that matches the FPM worker’s filesystem view: one authoritative root or alias, one intentional SCRIPT_FILENAME, an existence check before dispatch, and permissions that allow only the pool identity to read the application files.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




