The current Nginx configuration successfully handles most non-existent file requests by redirecting them to index.php, but fails when the request URI ends with .php. This creates an inconsistent behavior where some missing resources trigger the PHP front controller while others return 404 errors.
In your existing setup:
try_files $uri /index.php;
This directive tells Nginx to:
- First check if the requested URI exists as a physical file
- If not found, route to index.php
The problem occurs because of this block:
location ~ \\.php$ {
if (!-f $request_filename) {
return 404;
}
...
}
Here's the modified configuration that handles all cases consistently:
server {
listen 80 default_server;
access_log /path/to/site/dir/logs/access.log;
error_log /path/to/site/dir/logs/error.log;
root /path/to/site/dir/webroot;
index index.php index.html;
# Modified try_files directive
try_files $uri $uri/ /index.php$is_args$args;
location ~ \\.php$ {
try_files $uri =404; # First check file existence
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
1. Enhanced try_files directive: The modified version includes $is_args$args
to preserve query parameters when routing to index.php.
2. Simplified PHP handling: The if
statement is replaced with try_files
which is the preferred Nginx way to check file existence.
3. Proper SCRIPT_FILENAME: Using $document_root
makes the configuration more maintainable and portable.
With this configuration, all these test cases will now correctly route to index.php when the files don't exist:
example.com/non-existent-path
example.com/missing.php
example.com/subdir/not-found.html
example.com/valid.php?with=params
Only actual missing PHP files (when you want to return 404) will be properly handled by the try_files $uri =404
directive in the PHP location block.
This solution adds minimal overhead because:
- Nginx's
try_files
is highly optimized for file existence checks - The single fallback to index.php keeps the configuration simple
- No regex matching is needed for the primary routing logic
Many modern PHP frameworks and CMS platforms rely on front controllers (typically index.php) to handle routing. While Nginx's try_files
directive works well for most cases, there's a particular edge case with .php files that requires special attention.
Your existing configuration has this critical section:
location ~ \\.php$ {
if (!-f $request_filename) {
return 404;
}
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME /path/to/site/dir/webroot$fastcgi_script_name;
include /path/to/nginx/conf/fastcgi_params;
}
The issue occurs because Nginx processes PHP files in a special location block that checks for file existence before passing to FastCGI.
Here's the proper way to handle all cases:
server {
listen 80 default_server;
access_log /path/to/site/dir/logs/access.log;
error_log /path/to/site/dir/logs/error.log;
root /path/to/site/dir/webroot;
index index.php index.html;
# First try to serve request as file, then as directory, then fall back to index.php
try_files $uri $uri/ /index.php$is_args$args;
location ~ \\.php$ {
# Only check if the original PHP file exists if we're not already handling index.php
if ($fastcgi_script_name !~ "^/index\\.php") {
if (!-f $request_filename) {
rewrite ^ /index.php last;
}
}
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
}
- The
try_files
directive now includes$is_args$args
to preserve query parameters - Added conditional check in PHP location block to only verify file existence for non-index.php requests
- Used
rewrite
instead ofreturn
to properly handle the fallback - Simplified
SCRIPT_FILENAME
by using$document_root
With this configuration, all these cases will correctly route to index.php:
example.com/non-existent-path
example.com/missing-file.txt
example.com/non-existent.php
example.com/subdir/missing.php?param=value
This solution adds minimal overhead:
- Only one additional filesystem check for .php files
- No additional regex processing for non-PHP URIs
- Preserves Nginx's efficient static file handling
For popular frameworks:
# Laravel style
try_files $uri $uri/ /index.php?$query_string;
# Symfony style
try_files $uri /app.php$is_args$args;