Block Access to Directories With Nginx

When hosting an application, you may sometimes have directories in the public directory, which you don’t want to be accessible from the web. 90% of the web will explain how to use a .htaccess file, but this of course doesn’t work when using Nginx. After digging through various obscure posts, I finally managed to find and understand how Nginx roughly handles its configuration. Most importantly of course, how to block access to a directory.

Bear at a wild park

Here’s a basic Nginx config, which processes the request as:

  • Capture path ending in .php
    • Check the file exists, otherwise fallback to a 404
    • Call php-fpm
  • Capture all calls
    • Try to load the path as file from disk, otherwise…
    • Try to load the path as directory from disk, otherwise…
    • Redirect the call to /index.php and provide the arguments
server {
    listen 80;
    listen [::]:80;

    server_name example.com;

    root /var/www/example.com;
    index index.php index.html;

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

    location ~* \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

The location matching isn’t done in the order as specified but depends on the used modifier and the predefined processing order. The list goes as follows:

  • location = /path – Exact matching
  • location ^~ /path – Forward matching
  • location ~ /path – Regular expression case sensitive
  • location ~* \.(jpg|png)$ – Regular expression case insensitive
  • location /path – Catch all

Forward matching means that if this block is selected as the best non-regular expression match, no regular expressions will be executed.

For regular expression it’s important to note, that they will trigger for any subsection of a path. For example ~ /site will match example.com/site as well as example.com/api/site. In order to prevent any wrong matches, you may want to use the beginning ^ and end $ modifiers, for example ~ ^/site will then only match example.com/site, or ~ \.(jpg|png)$ will only match if the path ends in either .jpg or .png.

Armed with this knowledge, we should now be able to adjust the configuration for our desired outcome:

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

    server_name example.com;

    root /var/www/example.com;
    index index.php index.html;

    location ~ ^/(site|content|kirby) {
        return 404;
    }

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

    location ~* \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

With a case sensitive (use ~* for case insensitive match) we return a 404 for an path starting with either site, content or kirby.

That’s it!

Hope this helps anyone struggling with this setup. And if you’re wondering why I excluded the kirby directory, it’s because you’ll need this exact configuration when setting up Kirby CMS with Nginx, as the mentioned three directories shouldn’t be publicly accessible.

References

Leave a Comment

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.