'NginX: catch all file outside of root dir, with exceptions

I'm building a site running NginX / PHP and want all access to be processed by /private/routes.php. But I also want to add some exceptions to this for css/js-files and the odd php file in the public directory (and sub dirs).

Folder / file structure:

/var/www/domain.com
                   /private/routes.php
                   /public/test.php
                   /public/css/main.css
                   /public/js/main.js

My current NginX config:

server {
  listen 80;
  listen [::]:80;

  server_name domain.com;
  root /var/www/domain.com/public;

  #index index.php;

  #allow existing css files
  location /css/ {
     try_files $uri =404;
  }

  #allow my defined php files
  location /test.php {
     try_files $uri =404;
  }

  #route everything else to my catch-all file
  location / {
    root /var/www/domain.com;
    try_files /private/nada.php /private/routes.php;

    #must add php here because of redefined root
    location ~ \.php$ {
      include snippets/fastcgi-php.conf;
      fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }
  }

  #handle allowed php files
  location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
  }

}

The above config works but has some problems:

  1. I need to redefine root to run the catch all file (/private/routes.php).
  2. Because of #1 I need to add .php inside the location, so I end up with 2 php-location-configs.
  3. For some reason it doesn't work if I remove "/private/nada.php" from the "try_files".
  4. None-existing .php-files in /public returns 404 instead of being caught by the catch-all.

I could set the root to "/var/www/domain" but it seems a bit unnecessary since only the catch-all will use something else.

Is there any good ways to clean this up a bit?



Solution 1:[1]

Your configuration is really a mess. I'm not going to explain every mistake, otherwise it will make the answer 10 times longer. It took some time for me to figure out how it can be partially workable at all. The funniest thing here is that your location /test.php { ... } isn't really used to handle the /test.php request (or you'd got the test.php source code in response), however if you remove it, that request will be handled by the nested PHP handler instead of the root one.

Since the configuration you want to achieve does not fall into the usual use cases, you should not use that fastcgi-php.conf which is some kind of pre-written PHP-FPM handler usable in most situations. It isn't a part of the default nginx package provided by nginx team but is packaged with nginx by some distros, especially Debian-based ones. Since it is already includes the try_files directive, and we need to use custom try_files instead, we can't use that file or nginx will complain for duplicate try_files usage.

Here is a configuration that should work:

server {
    ...
    root /var/www/domain.com/public;

    # process an existing non-PHP file as a static one, fallback otherwise
    location / {
        try_files $uri @fallback;
    }

    # process an existing PHP file through PHP-FPM, fallback otherwise
    location ~ \.php$ {
        try_files $uri @fallback;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$uri;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }

    # fallback to /private/routes.php
    location @fallback {
        include fastcgi_params;
        # we don't need a "root" directive here
        # instead we can specify the fallback filename directly
        fastcgi_param SCRIPT_FILENAME /var/www/domain.com/private/routes.php;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }
}

This type of PHP-FPM usage is slightly different from the usual one since there are several FastCGI variables defined in the fastcgi_params file that depends on actual root used by nginx or actual $uri variable value:

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;

I've never seen any PHP script somehow relying on those, the common practice is to rely on REQUEST_URI variable value, but I think it worth to notice this difference. However these variables can be redefined, if needed (in that case this should be done after the include fastcgi_params; line). For example, the following will emulate the usual behavior of the PHP handler completely:

    location @fallback {
        include fastcgi_params;
        fastcgi_param SCRIPT_NAME /private/routes.php;
        fastcgi_param DOCUMENT_URI /private/routes.php;
        fastcgi_param DOCUMENT_ROOT /var/www/domain.com;
        fastcgi_param SCRIPT_FILENAME /var/www/domain.com/private/routes.php;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }

or you can define a different root, rewrite an URI and use the commonly used one:

    location @fallback {
        root /var/www/domain.com;
        rewrite ^ /private/routes.php break;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$uri;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }

However, as being already said, most likely it isn't required.

If under the public directory you have subdirectories containing index.php files and you want those files to be available without specifying the full path with the filename (e.g. domain.com/subdir/ rather than domain.com/subdir/index.php), change the root location to this one:

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1