Understanding Nginx try_files Directive in PHP-FPM Configuration: Why Static File Check Doesn’t Bypass FastCGI Processing


2 views

The try_files directive in Nginx serves as a conditional file existence checker, but crucially does not terminate request processing when used within location blocks that have additional processing instructions. Here's what happens step-by-step for a request to domain.com/test.php:

1. Nginx receives request for test.php
2. try_files checks if /usr/share/nginx/html/test.php exists
3. If exists, continues processing (does NOT serve statically)
4. Executes remaining directives in location block (fastcgi_pass)

The try_files $uri =404 line primarily serves two security purposes:

  • Prevents arbitrary code execution by verifying file existence before processing
  • Returns 404 if the PHP file doesn't exist (rather than passing to FastCGI)

This becomes clearer when we examine the processing flow:

location ~ \.php$ {
    try_files $uri =404;  # Security check only
    fastcgi_pass unix:/var/run/php-fpm.sock;
    # Other FastCGI params...
}

For different use cases, you might see these variations:

Basic Security Configuration:

try_files $uri =404;

With Fallback to Index:

try_files $uri $uri/ /index.php?$args;

Framework-Friendly (Laravel/Symfony):

try_files $uri /index.php$is_args$args;

Issue: try_files seems to bypass FastCGI
Solution: This usually indicates missing the PHP location block regex modifier (~ or ~*)

Issue: 404 errors on valid PHP files
Solution: Verify $document_root matches your file paths:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

While try_files adds a filesystem check, the overhead is minimal because:

  • Nginx caches negative lookups
  • Modern SSDs handle stat operations efficiently
  • The security benefit outweighs micro-optimization

For high-traffic sites, consider adding:

open_file_cache max=2000 inactive=20s;
open_file_cache_valid 30s;

In the given Nginx configuration, the try_files $uri =404 directive within the PHP location block serves a crucial security purpose. Contrary to initial assumptions, it doesn't simply serve static PHP files - rather, it performs a file existence check before proceeding with FastCGI processing.

location ~ \.php$ {
    try_files $uri =404; # Checks if file exists
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    # ... other fastcgi params ...
}

Here's what happens step-by-step when requesting domain.com/test.php:

The try_files $uri =404 line prevents arbitrary code execution vulnerabilities that could occur if:

  • Someone uploads malicious PHP files to upload directories
  • Path traversal attempts are made to execute system PHP files
  • Symlink attacks try to reference sensitive files

For more complex setups, you might see variations like:

location ~ \.php$ {
    try_files $uri /index.php$is_args$args;
    fastcgi_pass php-fpm;
    # ... fastcgi params ...
}

Be careful with these problematic patterns:

# UNSAFE: Missing try_files check
location ~ \.php$ {
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}

# RISKY: Overly permissive try_files
location ~ \.php$ {
    try_files $uri /index.php;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
}