Nginx Regex Location: How to Properly Handle Wildcard PHP Paths with FastCGI


2 views

When securing PHP files in Nginx, we often need to implement a blanket 404 for direct PHP file access while allowing specific entry points. The common approach results in repetitive configuration:

## Block all PHP files by default
location ~* ^.+\\.php$ {
    return 404;
}

## Whitelist specific entry points
location = /sitename/subpage/index.php {
    fastcgi_pass phpcgi;
}

location = /sitename/subpage2/index.php {
    fastcgi_pass phpcgi;
}

location = /sitename/subpage3/index.php {
    fastcgi_pass phpcgi;
}

The intuitive solutions don't work as expected due to Nginx's location matching priorities:

## These WON'T work:
location = /sitename/*/index.php {
    fastcgi_pass phpcgi;
}

location ~* ^/sitename/[a-z]/index.php$ {
    fastcgi_pass phpcgi;
}

The first fails because = matches require exact paths. The second might match but gets overridden by the catch-all PHP block due to Nginx's precedence rules.

Here's the proper way to handle this with regex locations while maintaining security:

## Primary PHP blocker remains first
location ~* ^.+\\.php$ {
    return 404;
}

## Whitelist pattern for subdirectory index.php files
location ~ ^/sitename/([^/]+)/index\\.php$ {
    try_files $uri =404;
    fastcgi_pass phpcgi;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

1. Order matters: The whitelist must come AFTER the catch-all PHP block
2. Regex specificity: The pattern ^/sitename/([^/]+)/index\\.php$ ensures we only match one subdirectory level
3. Security: try_files $uri =404 verifies file existence before processing

For more complex routing needs:

## Multiple subdirectory levels
location ~ ^/sitename/([^/]+(/[^/]+)*)/index\\.php$ {
    fastcgi_pass phpcgi;
    # ... other fastcgi params
}

## Specific subdirectory patterns
location ~ ^/sitename/(blog|shop|admin)/index\\.php$ {
    fastcgi_pass phpcgi;
    # ... other fastcgi params
}

## With additional parameters
location ~ ^/sitename/([^/]+)/index\\.php(\\?.*)?$ {
    fastcgi_pass phpcgi;
    # ... other fastcgi params
}

When securing PHP files in Nginx, we often want to block direct access to most .php files while allowing specific ones. The initial approach of listing each allowed file individually creates maintenance headaches:

location = /sitename/subpage/index.php {
    fastcgi_pass phpcgi;
}

location = /sitename/subpage2/index.php {
    fastcgi_pass phpcgi;
}

The intuitive wildcard approaches don't work because:

  1. Nginx doesn't support glob patterns (*) in exact match locations (location =)
  2. Regex patterns have different matching behavior than exact matches

Here's the working regex approach that maintains security while reducing config bloat:

# Block all PHP files by default
location ~* \.php$ {
    return 404;
}

# Allow specific index.php files in subdirectories
location ~ ^/sitename/([a-z0-9_-]+)/index\.php$ {
    fastcgi_pass phpcgi;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

For more complex scenarios, consider these patterns:

# Match numeric subdirectories only
location ~ ^/sitename/(\d+)/index\.php$ {
    fastcgi_pass phpcgi;
}

# Match multiple directory levels
location ~ ^/sitename/(.+)/index\.php$ {
    fastcgi_pass phpcgi;
}

# Whitelist specific subdirectories
location ~ ^/sitename/(subpage|subpage2|special)/index\.php$ {
    fastcgi_pass phpcgi;
}

Remember that:

  • Regex locations are evaluated in order until the first match
  • Prefix-based locations (location /) are faster than regex
  • Use ^~ modifier for prefix locations that shouldn't be overridden by regex
# Performance-optimized version
location /sitename/ {
    try_files $uri $uri/ =404;
}

location ~ ^/sitename/([a-z0-9_-]+)/index\.php$ {
    fastcgi_pass phpcgi;
}

location ~ \.php$ {
    return 404;
}