How to Configure Nginx to Route Missing Files (Including .php) to index.php


2 views

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:

  1. First check if the requested URI exists as a physical file
  2. 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 of return 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;