Understanding Nginx Try Files
The nginx try_files
directive is actually interesting! Not, like, amazingly interesting - but it has more depth than appears at first glance.
First, Nginx almost doesn't need try_files
. Without it, Nginx could serve static files just fine:
server {
listen 80;
server_name _;
root /var/www/html/public;
index index.html index.html;
}
If we support PHP, we could have something like this:
server {
listen 80;
server_name _;
root /var/www/html/public;
index index.html index.html;
location ~ \.php$ {
# pass off to PHP-FPM via fastcgi
}
}
That actually works for static files and the home page of our PHP application. Once we introduce a path into our URI (e.g. example.com/foo/bar)
, it breaks. This is where try_files
comes in.
Adding try_files
The try_files
directive is going to run through each option given in order to attempt to use its directives to find a file that exists on the server.
server {
listen 80;
server_name _;
root /var/www/html/public;
index index.html index.html index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
For a given URI, this will do the following:
The $uri
option tells try_files
to find the URI as given as a file on the disk drive relative to the root
, which is /var/www/html/public
in this case.
1️⃣ A URI of /css/app.css
will search in /var/www/html/public/css/app.css
.
2️⃣ A URI of /foo/bar
will have 2 behaviors - one for if the directory exists, and one for if it does not.
First, the $uri/
option tells try_files
to treat the URI as a directory and see if a directory exists. If the URI relates to an existing directory, Nginx needs to figure out what file to serve from that directory.
That's where the index
directive comes into play. Since Nginx just knows a directory exist, we need index
to tell Nginx which files to serve out of there (if they exist). You can have any files there. The first matched file "wins" and is served.
3️⃣ If the given URI matches neither an existing file nor directory, then try_files
goes to the fallback URI - /index.php?$query_string;
.
But other location blocks?
The location /
block, and the try_files
directive within it, actually work together with other location
blocks! Here's slightly more complete configuration file:
server {
listen 80;
server_name _;
root /var/www/html/foobar.com;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
# pass off to PHP-FPM via fastcgi
}
location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico)$ {
expires 7d;
access_log off;
log_not_found off;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
If the try_files
directive resolves/finds a file that is a static asset (css, js, image), then the third location block actually handles the request. That means both location / {}
and location ~* \.(<stuff>)$ {}
blocks are relevant to such a request!
The same is true for when PHP files are used - the location ~ \.php$ {}
block is used:
- When
index
resolves toindex.php
in a directory - When the fallback
/index.php?$requests_uri
is used - When a PHP file given by the URI exists as given
In these cases, the "matched" (used?) PHP file found by try_files
is handled by the location ~ \.php$ {}
block, which passes the request off to PHP-FPM. This is why a 404 error (whether for a static file or a non-existent application route) is generally returned from the PHP application. All real "can't find a file on the disk" cases are passed off to /index.php
, and therefore the request is sent to the PHP application proxied via FastCGI in this configuration.