A page was working fine. Nginx was restarted after a config edit — and the browser shows a white screen with '404 Not Found'. Or another scenario: the site was migrated to a new server, everything was copied over, but half the URLs return 404. Both situations come up more often than expected — and both are resolved in minutes once you know where to look.
What 404 Means in the Nginx Context
HTTP 404 means one thing: the server received and understood the request, but did not find a resource at the specified path. Nginx acts either as a web server (serving files from disk) or as a proxy (forwarding requests to a backend). In both cases, 404 is not a server crash — it is a deliberate response.
An important distinction: Nginx returns 404 on its own when a file is not found on disk. But it can also proxy a 404 from a backend (PHP-FPM, Node.js, Python). These are different situations with different diagnostic approaches.
Quick Diagnosis: Check the Logs
Before touching the config — open the error log. The answer is usually already written there:
sudo tail -f /var/log/nginx/error.log
A typical line that explains a 404:
open() "/var/www/html/about" failed (2: No such file or directory)
Nginx is looking for a file named about in /var/www/html/ and not finding it. The cause is immediately clear.
For a specific domain with separate virtual host logs:
sudo tail -f /var/log/nginx/yourdomain.com.error.log
Cause 1: Wrong root or alias Path
The most common cause on VPS servers — a typo or wrong path in the root directive inside a server or location block.
Check the site config:
sudo nano /etc/nginx/sites-available/yourdomain.com
Verify the directory exists:
ls -la /var/www/mysite/public
If the directory exists but Nginx still returns 404, check file permissions. Nginx runs as www-data (Debian/Ubuntu) or nginx (CentOS/RHEL) and needs read access:
sudo chown -R www-data:www-data /var/www/mysite
sudo chmod -R 755 /var/www/mysite
Cause 2: try_files Not Configured for SPAs and CMS
Single-page applications (React, Vue, Angular) and most CMS platforms (WordPress, Laravel) use a single index.php or index.html as the entry point. Routes like /about or /catalog/item-5 are virtual — those files do not exist on disk, and Nginx correctly returns 404.
The fix is the try_files directive:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
For static SPA builds (React/Vue):
location / {
try_files $uri $uri/ /index.html;
}
How it works: Nginx first looks for an exact file match ($uri), then a directory ($uri/), and if nothing is found — passes the request to index.php or index.html. Without this directive, any nested route returns 404.
Cause 3: A location Block Catches the Wrong Requests
Nginx processes location blocks in a defined priority order — and sometimes one block intercepts requests intended for another.
Validate the config syntax:
sudo nginx -t
Test a specific URL response:
curl -I https://yourdomain.com/api/users
A typical problem — a static assets block intercepts API requests:
location ~* \.(js|css|png|jpg)$ {
# this block should not match /api/users
}
If the location pattern is written incorrectly, API requests go to the static handler and get 404 because no file named api/users exists on disk.
Cause 4: fastcgi_pass or proxy_pass Not Working
When Nginx proxies requests to PHP-FPM or another backend, the 404 may come from the backend itself, not from Nginx.
Check that PHP-FPM is running:
sudo systemctl status php8.1-fpm
Verify the socket path in the config:
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Check the actual socket path on disk:
ls /var/run/php/
If the socket path in the config does not match the real path, Nginx gets a connection error and may return 404 or 502.
Cause 5: File Exists but No Index File in Directory
Nginx does not serve a directory as a file. If a request hits /about/ and there is no index.html inside — the result is 404 (or 403 if directory listing is disabled).
Add the index directive:
location / {
index index.html index.php;
}
Enable directory listing (only for file servers, not regular sites):
location /files/ {
autoindex on;
}
Set Up a Custom 404 Page
The default Nginx 404 page looks bare and exposes the server version. Replace it:
server {
error_page 404 /404.html;
location = /404.html {
root /var/www/html;
internal;
}
}
internal blocks direct external access to /404.html — it is only served as an error response.
Intercept backend 404 responses and show a custom page:
proxy_intercept_errors on;
error_page 404 /custom_404.html;
Redirect Old URLs Instead of Showing 404
If the site structure changed, the right fix is not to 'repair the 404' but to set up redirects. Search engines and users will follow the new URLs:
location = /old-page {
return 301 /new-page;
}
Pattern-based redirect (e.g., from /category/id to /catalog/id):
rewrite ^/category/(.*)$ /catalog/$1 permanent;
Apply Changes
After any config edit, test syntax first:
sudo nginx -t
If the test passes:
sudo systemctl reload nginx
reload applies the config without interrupting active connections — unlike restart.
Diagnostic Quick Reference
| Symptom | Where to Look |
|---|---|
| 404 on all URLs | root directive path, directory existence |
| 404 on nested routes only | try_files directive |
| 404 only on .php files | PHP-FPM status, socket path |
| 404 after server migration | Directory permissions (chown, chmod) |
| 404 on old URLs after redesign | 301 redirects via rewrite |
| Error log | sudo tail -f /var/log/nginx/error.log |
| Config validation | sudo nginx -t |
| Apply changes | sudo systemctl reload nginx |