Site icon GetPageSpeed

NGINX try_files: Complete Guide with Real Examples

NGINX try_files: Complete Guide with Real Examples

The NGINX try_files directive routes requests by testing multiple file paths in sequence. It powers WordPress pretty permalinks, Laravel routing, and single-page application (SPA) deployments. However, many tutorials overlook critical performance considerations. This comprehensive guide covers everything you need to know about nginx try_files.

What is NGINX try_files?

The nginx try_files directive checks whether files or directories exist on disk. NGINX tests each path in order until it finds a match. The last parameter serves as the fallback when all previous checks fail.

Here is the basic syntax:

try_files file1 file2 ... fallback;

NGINX processes try_files during the PRECONTENT_PHASE. This means the directive runs after access control but before content handlers. Therefore, try_files can rewrite the request URI before static file serving or FastCGI processing begins.

NGINX try_files Syntax Explained

The try_files directive accepts two or more parameters. You can use it in server and location contexts. Let’s examine each component in detail.

File Paths and Variables

Each parameter can be a static path or contain variables:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

Common variables include:

Variable Description
$uri The normalized request URI
$document_root The root directive value
$query_string Query parameters after ?
$args Alias for $query_string

The $uri variable is the most commonly used parameter. It contains the normalized request path without query string parameters. For example, a request to /blog/post?page=2 sets $uri to /blog/post.

Directory Testing with Trailing Slash

Add a trailing slash to test for directories:

try_files $uri $uri/ =404;

In this example, $uri/ checks if a directory exists. NGINX then looks for an index file within that directory based on the index directive. The index directive specifies which files NGINX should serve when a directory is requested.

Fallback Options

The last parameter is always the fallback. You have three options:

1. Internal Redirect to URI:

try_files $uri $uri/ /index.php;

This pattern redirects internally to index.php when no file or directory matches. NGINX processes the request as if the client originally requested /index.php.

2. Named Location:

try_files $uri $uri/ @backend;

location @backend {
    proxy_pass http://upstream;
}

Named locations provide clean separation of concerns. They cannot be accessed directly by clients, which adds a layer of security to your configuration.

3. HTTP Status Code:

try_files $uri =404;

The status code format uses = followed by the code. Valid codes range from 0 to 999. Use this when you want to return an error for missing files.

How NGINX try_files Works Internally

Understanding the internal mechanics helps you write better configurations. NGINX pre-compiles variable references at configuration load time. This optimization eliminates runtime parsing overhead.

For each request, NGINX:

  1. Calculates the full filesystem path from each parameter
  2. Uses ngx_open_cached_file() to check file existence
  3. Returns immediately upon finding a valid file
  4. Falls back to the last parameter if all checks fail

The open file cache integration is crucial for performance. Without caching, NGINX performs stat() syscalls for every file in the try_files chain on every request. This becomes expensive under high traffic conditions.

Additionally, NGINX handles alias configurations specially when using try_files. The module carefully reconstructs paths to maintain proper alias semantics. This allows try_files to work correctly with both root and alias directives.

Common NGINX try_files Patterns

Let’s explore practical configurations for different use cases. Each pattern addresses specific routing requirements.

Static File Serving with 404 Fallback

The simplest pattern serves static files and returns 404 for missing resources:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # Performance optimization - required for try_files
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    location / {
        try_files $uri $uri/ =404;
    }
}

This configuration first checks if the exact file exists. Then it tests for a directory with an index file. Finally, it returns a 404 error. This pattern works well for static websites and documentation sites.

WordPress NGINX try_files Configuration

WordPress requires all requests for non-existent files to route through index.php. Here is the standard WordPress configuration:

server {
    listen 80;
    server_name wordpress.example.com;
    root /var/www/wordpress;
    index index.php index.html;

    # Performance optimization
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

The $args variable preserves query string parameters. This ensures WordPress receives pagination and search parameters correctly. Without this, WordPress pretty permalinks would break.

For more WordPress optimization, see our guide on NGINX FastCGI Keepalive for persistent PHP-FPM connections.

Laravel NGINX try_files Configuration

Laravel applications route all requests through public/index.php. The configuration is similar to WordPress but with important differences:

server {
    listen 80;
    server_name laravel.example.com;
    root /var/www/laravel/public;
    index index.php;

    # Performance optimization
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Notice the root points to Laravel’s public directory. This is essential for security, as it prevents direct access to configuration files. Laravel stores sensitive files like .env outside the public directory.

Single Page Application (SPA) Routing

React, Vue, and Angular applications handle routing client-side. NGINX must serve index.html for all routes:

server {
    listen 80;
    server_name spa.example.com;
    root /var/www/spa/dist;
    index index.html;

    # Performance optimization
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # Serve static assets directly
    location /assets/ {
        try_files $uri =404;
    }

    # All other routes go to index.html
    location / {
        try_files $uri $uri/ /index.html;
    }
}

Static assets like JavaScript and CSS files are served directly from the assets directory. All other requests receive the SPA’s index.html for client-side routing. The JavaScript framework then handles route matching on the client side.

Named Location Pattern

Named locations provide cleaner configurations for complex routing. They begin with the @ symbol:

server {
    listen 80;
    server_name api.example.com;
    root /var/www/api/public;

    # Performance optimization
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    location / {
        try_files $uri $uri/ @backend;
    }

    location @backend {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Named locations cannot be accessed directly by clients. They only execute through internal redirects from try_files or error_page directives. This design pattern keeps your configuration organized and maintainable.

For more on proxy configuration, see our NGINX Reverse Proxy guide.

Performance Optimization with open_file_cache

The try_files directive performs filesystem lookups for each path. Without caching, this creates significant overhead. The open_file_cache directive caches file metadata to eliminate redundant syscalls.

Why open_file_cache Matters

Consider this configuration without caching:

location / {
    try_files $uri $uri/ /index.php?$args;
}

For a request to /blog/post-title, NGINX executes:

  1. stat("/var/www/html/blog/post-title") – file check
  2. stat("/var/www/html/blog/post-title/") – directory check
  3. Falls back to /index.php

These syscalls happen on every single request. On a busy site handling thousands of requests per second, this overhead adds up quickly. Moreover, disk I/O becomes a bottleneck even with modern SSDs.

With open_file_cache, results are cached:

# Cache up to 1000 file descriptors
open_file_cache max=1000 inactive=20s;

# Revalidate cached entries after 30 seconds
open_file_cache_valid 30s;

# Cache entries used at least 2 times
open_file_cache_min_uses 2;

# Cache "file not found" errors
open_file_cache_errors on;

The security tool gixy specifically warns when try_files is used without open_file_cache. The warning categorizes this as a MEDIUM severity issue. Consequently, you should always pair these directives together.

For most production servers:

http {
    # Global open_file_cache settings
    open_file_cache max=2000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # ... server blocks
}

Adjust max based on your file count. Busy sites with many static assets may need higher values. Furthermore, monitor your server’s memory usage when increasing cache sizes.

Common NGINX try_files Mistakes

Avoid these frequent configuration errors that can impact performance and functionality.

Missing open_file_cache

The most common mistake is omitting open_file_cache:

# Bad: No caching leads to excessive syscalls
location / {
    try_files $uri $uri/ =404;
}

Always include open_file_cache when using try_files. This simple addition dramatically improves performance on high-traffic sites.

Incorrect Path Order

Order matters in try_files. Place more specific paths first:

# Bad: Directory check before file check is inefficient
location / {
    try_files $uri/ $uri =404;
}

# Good: File check first, then directory
location / {
    try_files $uri $uri/ =404;
}

File checks are faster than directory checks because they require fewer operations. Place them first for better performance.

Using try_files with if Blocks

Combining try_files with if blocks can cause unexpected behavior. The NGINX if is evil guide explains why in detail.

# Risky: if directive with try_files
location / {
    if ($request_uri ~ "^/admin") {
        # This might not work as expected
    }
    try_files $uri $uri/ /index.php;
}

Use map directives or separate location blocks instead. These alternatives provide predictable behavior.

Forgetting the Fallback

Every try_files directive needs a fallback:

# Wrong: No fallback - NGINX will reject this
location / {
    try_files $uri;
}

# Correct: Always include a fallback
location / {
    try_files $uri =404;
}

NGINX requires at least two parameters for try_files. The configuration test will fail without a proper fallback.

Security Considerations

The try_files directive interacts with file permissions and paths. Keep these security points in mind when configuring your server.

Prevent Directory Traversal

The try_files directive uses normalized URIs. NGINX removes .. sequences before processing. However, verify your alias vs root configuration to prevent traversal issues.

Protect Sensitive Files

Exclude sensitive files from being served:

# Block access to hidden files
location ~ /\. {
    deny all;
}

# Block access to sensitive extensions
location ~ \.(env|git|svn|htaccess)$ {
    deny all;
}

location / {
    try_files $uri $uri/ /index.php;
}

Place deny rules before try_files locations. NGINX location priority determines which block handles each request.

Validate Configuration with Gixy

Use gixy to audit your NGINX configuration:

gixy /etc/nginx/nginx.conf

Gixy checks for common security issues including:

Running gixy regularly helps catch configuration mistakes before they reach production.

NGINX try_files vs rewrite

Both directives can route requests, but they serve different purposes. Understanding when to use each helps you write cleaner configurations.

When to Use try_files

Use try_files when:

# Good use of try_files
location / {
    try_files $uri $uri/ /index.php;
}

The try_files directive excels at conditional routing based on filesystem state.

When to Use rewrite

Use rewrite when:

# Good use of rewrite
rewrite ^/old-path/(.*)$ /new-path/$1 permanent;

See our NGINX Rewrite Rules guide for detailed examples.

The key difference is that try_files tests file existence while rewrite transforms URIs unconditionally. Choose the appropriate directive based on your requirements.

Debugging NGINX try_files

When try_files doesn’t work as expected, enable debug logging:

error_log /var/log/nginx/error.log debug;

Debug output shows each file NGINX tests:

[debug] try files handler
[debug] trying file: "/var/www/html/blog/post"
[debug] trying file: "/var/www/html/blog/post/"
[debug] trying fallback: "/index.php"

Common issues to check:

  1. Root path: Verify root or alias points to the correct directory
  2. Permissions: Ensure NGINX can read files and traverse directories
  3. SELinux: On RHEL-based systems, check SELinux contexts with ls -Z
  4. Index directive: Confirm index includes your default file

For permission issues, see our guide on optimizing NGINX for PHP websites.

Complete Production Configuration

Here is a production-ready configuration combining all best practices:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/example.com/public;
    index index.php index.html;

    # Security headers
    server_tokens off;

    # Performance: Open file cache for try_files
    open_file_cache max=2000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # Block sensitive files
    location ~ /\. {
        deny all;
    }

    # Static assets with long cache
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?)$ {
        try_files $uri =404;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Front controller pattern
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP-FPM handling
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

This configuration includes:

Summary

The NGINX try_files directive is essential for modern web applications. It enables clean URLs for WordPress, Laravel, and single-page applications. However, proper configuration requires attention to performance and security.

Key takeaways:

  1. Always use open_file_cache with try_files to avoid excessive syscalls
  2. Order paths correctly – file checks before directory checks
  3. Include a fallback as the last parameter
  4. Validate with gixy to catch common misconfigurations
  5. Use named locations for complex routing scenarios

For additional NGINX optimization, explore our guides on browser caching, gzip compression, and proxy caching.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Exit mobile version