How to Properly Convert Nginx Rewrite Rules to try_files Directive for Clean URL Handling


2 views

The original configuration uses conditional rewrites to handle two URL patterns:

location / {
   if ( !-f $request_filename ) {
     rewrite ^/([a-z]*)$ /index.php?action=$1;
     rewrite ^/([a-z]*)/(.*)$ /index.php?action=$1&item=$2;
   }
}

This handles both simple actions (/contact) and action-item pairs (/products/123). The new version needs to maintain this functionality while using try_files for better performance.

The attempted conversion has several issues:

try_files $uri $uri/ /index.php?action=$uri&item=$args;

1. $uri includes the leading slash which breaks parameter parsing
2. No pattern matching for action-item separation
3. $args contains original query string, not path components

Here's the corrected implementation that matches the original rewrite behavior:

server {
    listen 80 default;
    root /var/www;
    index index.php;
    server_name _;

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

    location @rewrite {
        rewrite ^/([a-z]+)(?:/(.*))?$ /index.php?action=$1&item=$2 break;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

For even cleaner configuration:

location @rewrite {
    rewrite ^/(?[a-z]+)(?:/(?.*))?$ /index.php?action=$action&item=$item break;
}

The try_files approach is more efficient than if conditions because:

  • Reduced directive processing overhead
  • Clearer file existence checking flow
  • Better Nginx internal optimization

Verify with these test cases:

curl http://localhost/contact       # Should map to action=contact
curl http://localhost/products/123  # Should map to action=products&item=123
curl http://localhost/style.css     # Should serve static file directly

Many developers transitioning from older Nginx versions often struggle with converting legacy rewrite rules to the modern try_files approach. The original configuration was handling two patterns:

rewrite ^/([a-z]*)$ /index.php?action=$1;
rewrite ^/([a-z]*)/(.*)$ /index.php?action=$1&item=$2;

The attempted conversion using try_files doesn't properly capture these patterns, resulting in 404 errors for dynamic routes.

The current implementation:

try_files $uri $uri/ /index.php?action=$uri&item=$args;

has several issues:

  • It passes the full URI as the action parameter
  • Doesn't properly separate the action from item components
  • Handles query parameters incorrectly

Here's how to properly implement the routing with try_files:

server {
    listen 80 default;
    root /var/www;
    index index.php;
    server_name _;

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

    location @rewrite {
        rewrite ^/([a-z]+)/?$ /index.php?action=$1 last;
        rewrite ^/([a-z]+)/([^/]+)/?$ /index.php?action=$1&item=$2 last;
        rewrite ^/([a-z]+)/([^/]+)/(.*)$ /index.php?action=$1&item=$2&params=$3 last;
    }

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

For better maintainability, consider this pattern:

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

location @phprouter {
    if ($uri ~ ^/([a-z]+)(?:/([^/]+)(?:/(.*))?)?$) {
        set $action $1;
        set $item $2;
        set $params $3;
        rewrite ^ /index.php?action=$action&item=$item&params=$params break;
    }
}
  • Always include fastcgi_param SCRIPT_FILENAME in PHP location
  • Use last flag for final rewrite rules
  • Consider security implications of dynamic routing
  • Test with various URL patterns: /about, /products/item123, /category/sub/item

For high-traffic sites, add caching:

location @phprouter {
    # Cache regex matches
    if ($uri ~ ^/([a-z]+)(?:/([^/]+))?$) {
        set $action $1;
        set $item $2;
        rewrite ^ /index.php?action=$action&item=$item break;
    }
    
    # Fallback for longer URLs
    rewrite ^/([^/]+)(?:/([^/]+)(?:/(.*))?)?$ /index.php?action=$1&item=$2&params=$3 break;
}