You open your site and get a bare white page that says File not found. The browser shows a 404, but the page doesn't look like your theme's 404 at all. Then you check the Nginx error log and find the real message: FastCGI sent in stderr: "Primary script unknown". It's one of the most common errors on a fresh Nginx + PHP-FPM VPS, and it's almost always a one-line mistake.
Here's what it means, how to find the cause in a couple of minutes, and how to stop it coming back.
Symptom: what "Primary script unknown" looks like
The browser just shows File not found. in plain text. There's no Nginx 404 page, no cpanel" class="auto-link">WordPress 404 page. That plain text is generated by PHP-FPM itself, not by Nginx. In the log you'll see something like:
tail -n 20 /var/log/nginx/error.log
FastCGI sent in stderr: "Primary script unknown" while reading response header
from upstream, request: "GET /index.php HTTP/1.1",
upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:"
So Nginx did its job and handed the request to PHP-FPM. PHP-FPM then looked for the file Nginx told it about, and couldn't open it.
Cause: PHP-FPM can't find or read the script
Nginx doesn't run PHP. It passes a variable called SCRIPT_FILENAME to PHP-FPM, and PHP-FPM tries to open that exact path. "Primary script unknown" means one of these went wrong:
| Cause | How to spot it |
|---|---|
Wrong root in the server block | The path in the log doesn't exist on disk |
SCRIPT_FILENAME built from the wrong variable | Path looks like /index.php or has a doubled folder |
| Permissions or ownership | File exists, but the PHP-FPM user can't traverse the folders |
chroot or open_basedir on the pool | Path is valid for Nginx, invalid inside the pool |
| Symlinked release folders (deploys) | Breaks right after a deploy; the old release was removed |
| Typo or wrong site enabled | Config edited, but another server block is answering |
Fix 1: check the root and SCRIPT_FILENAME
Open your site's server block, usually in /etc/nginx/sites-available/example.com or /etc/nginx/conf.d/example.com.conf. A working PHP block looks like this:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
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.3-fpm.sock;
}
}
Three things to check:
- The
rootpath really exists. Runls -l /var/www/example.com/public_html/index.php. - If you wrote your own
fastcgi_paramlines instead of using the snippet, make sure you havefastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;. A missing$document_rootis the classic cause. - The
rootdirective isn't tucked inside alocationblock that doesn't apply to.phprequests. Move it up to theserverlevel.
Test and reload:
sudo nginx -t
sudo systemctl reload nginx
Fix 2: permissions the PHP-FPM user can actually use
Nginx runs as www-data (Debian/Ubuntu) or nginx (AlmaLinux/Rocky). PHP-FPM may run as a different user, especially if you set up per-site pools. That user needs execute (x) on every folder in the path, and read on the file.
namei -l /var/www/example.com/public_html/index.php
grep -E '^(user|group|listen.owner)' /etc/php/8.3/fpm/pool.d/www.conf
namei -l prints the permissions of every folder along the path. If you see a directory like /var/www/example.com set to drwx------ and owned by another user, that's your culprit. A safe baseline:
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
Skip chmod 777. It hides the problem and opens a security hole.
Fix 3: open_basedir, chroot and per-pool settings
If the file definitely exists and permissions look right, check the pool config for chroot or php_admin_value[open_basedir]:
grep -RnE 'chroot|open_basedir' /etc/php/8.3/fpm/pool.d/
With a chroot, the path PHP sees is relative to the chroot folder, so Nginx's $document_root won't match. Either drop the chroot or use $realpath_root carefully with matching paths. An open_basedir that doesn't include your web root will also trigger this.
Fix 4: symlinked deploys and $realpath_root
If you deploy with a current symlink pointing at a release folder, PHP-FPM's opcode cache can hold the old resolved path after a deploy. Use $realpath_root and reload PHP-FPM after switching releases:
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
sudo systemctl reload php8.3-fpm
Fix 5: make sure the right server block answers
Sometimes you edit the correct file and nothing changes, because a different server block is handling the request. Dump the full active config and look:
sudo nginx -T | grep -nE 'server_name|root '
Check that the file is actually linked: ls -l /etc/nginx/sites-enabled/. And confirm the PHP-FPM socket in fastcgi_pass matches the one that exists in /run/php/. A wrong socket gives a 502, not this error, but a wrong version pool with a different root can give you this one.
Quick diagnosis flow
- Read the exact path in the Nginx error log (and the PHP-FPM log at
/var/log/php8.3-fpm.log). - Run
ls -lon that exact path. Doesn't exist? FixrootorSCRIPT_FILENAME. - Exists? Run
namei -land fix permissions or ownership. - Still failing? Look for
chroot,open_basedirand symlinks. - Always finish with
nginx -tand a reload.
Prevention
- Use the packaged
snippets/fastcgi-php.conf(orfastcgi_paramsplus a correctSCRIPT_FILENAME) instead of hand-rolling parameters. - Keep one pool per site with a clear owner, and document it in the server block as a comment.
- After a deploy that swaps folders, reload PHP-FPM as part of the script.
- Run
nginx -tbefore every reload. Put it in your deploy script so a bad config never goes live. - Keep web roots under
/var/wwwwith755directories so both Nginx and PHP-FPM can traverse them.
If you're on a SkyServer VPS and the log shows a path you can't make sense of, send our support team the output of sudo nginx -T and the last 20 lines of the error log. That's usually all we need to point at the exact line.
Frequently Asked Questions
Why does the page say "File not found." instead of a normal 404?
Because the message comes from PHP-FPM, not from Nginx or WordPress. PHP-FPM couldn't open the script Nginx asked for, so it returns that plain text with a 404 status.
Is "Primary script unknown" the same as a 502 Bad Gateway?
No. A 502 means Nginx couldn't talk to PHP-FPM at all (socket missing, service down). Here the connection works; PHP-FPM just can't find the file.
Will chmod 777 fix it?
It might appear to, but it's the wrong fix. If permissions were the cause, set directories to 755 and files to 644 with the right owner. If the path was wrong, no permission change will help.
It only happens on some URLs, not the homepage. Why?
That usually points at try_files. Without a fallback to /index.php?$args, pretty URLs get passed to PHP as non-existent files. Add the fallback line shown above.
Does this happen on cPanel hosting too?
Rarely, since cPanel configures the path for you. If you see it on a cPanel server running Nginx as a proxy, check that the document root of the domain wasn't moved or renamed.
