Score:1

What is the difference beween $uri and $uri/ in the try_files directive?

in flag

This is the configuration:

index index.html;
location / {
    try_files $uri $uri/ = 404;
}

I make a request at http://localhost/path/a/. I assume this is the URI, so at the $uri stage try_files will look at /path/a/ and serve index.html if there's any.

I read from the docs that It is possible to check directory’s existence by specifying a slash at the end of a name, e.g. “$uri/”. but this does not make any sense for me.

Score:0
gr flag

It is exactly the $uri/ part that makes nginx assuming an URI can be a directory name and looking for an index file presence inside it.

Assume the following directory structure:

/var/www/html
         ├── a
         │   └── index.html
         ├── b
         │   ├── index.html
         │   └── index.htm
         ├── c
         │   └── test.html
         ...

and the following nginx config:

server {
    root /var/www/html;
    index index.htm index.html;
    location / {
        try_files $uri $uri/ =404;
    }
}

Here is what happened with the following curl requests:

  • curl http://localhost/a
    

    Returns a redirect:

    HTTP/1.1 301 Moved Permanently
    Location: http://localhost/a/
    
  • curl http://localhost/a/
    

    Returns the /var/www/html/a/index.html file contents.

  • curl http://localhost/b
    

    Returns a redirect:

    HTTP/1.1 301 Moved Permanently
    Location: http://localhost/b/
    
  • curl http://localhost/b/
    

    Returns the /var/www/html/b/index.htm file contents (index files existence is checked in order they are specified using the index directive).

  • curl http://localhost/c
    

    Returns a redirect:

    HTTP/1.1 301 Moved Permanently
    Location: http://localhost/c/
    
  • curl http://localhost/c/
    

    Since there is no index file in the /var/www/html/c/ directory, curl command will return an HTTP 403 Forbidden unless you have an autoindex on; directive in your config (in that case nginx will return the /var/www/html/c/ directory listing).

If your nginx config will look like

server {
    root /var/www/html;
    index index.htm index.html;
    location / {
        try_files $uri =404;
    }
}

each of the above requests now will return an HTTP 404 Not Found error. An autoindex directive, if being present, will have no effect. The only way to get an index.html contents will be to specify it explicitly, e.g. http://localhost/a/index.html, http://localhost/b/index.htm etc.

The very important yet absolutely non-obvious thing is that an index directive being used with try_files $uri $uri/ =404 can cause an internal redirect. For example, if you will have the following configuration:

server {
    root /var/www/html;
    index index.htm index.html;
    location / {
        add_header X-Test test1;
        try_files $uri $uri/ =404;
    }
    location /a/index.html {
        add_header X-Test test2;
    }
}

request http://localhost/a/ will cause an internal redirect from /a/ to /a/index.html and return the /var/www/html/a/index.html file contents with the custom X-Test header set to test2, not to test1!

The last thing worth to mention is that try_files $uri $uri/ =404; is the default nginx behavior, so

location / {
    try_files $uri $uri/ =404;
}

and

location / {}

locations are totally equal.

Update

An additional question from OP:

My thoughts were: $uri examines URI as it is and $uri/ examines URI as a directory looking out for an index file. For http://localhost/a with try_files $uri /file.html =404; I get file.html. Good for now! For http://localhost/a with try_files $uri/ /file.html =404; I get file.html too. Why? I was expecting the index.html. Furthermore, try_files $uri $uri/ /file.html =404; will get me the index.html.

A really good question! Without answering it the whole answer will be somehow incomplete. Let's look what happens here.

Having the http://localhost/a/ request and try_files $uri/ /file.html =404; directive in your nginx config, on a first step nginx checked the /var/www/html/a/ directory for being a directory, next checked it for an index file presence, found an index.html file inside it and made an internal redirect from /a/ to /a/index.html. On a second step, being inside the same location block, nginx checks the /var/www/html/a/index.html for being a directory, but it isn't! And since you don't have an $uri component as a try_files directive parameter, it goes for the next check for the /var/www/html/file.html file, found it and returns its content.

You may think that using the try_files directive without an $uri parameter is completely useless therefore. Usually it is, but it can be a use case to do it, for example when you want to hide your internal site structure. Here is an example:

server {
    root /var/www/html;
    index index.html;
    location / {
        try_files $uri/ =404;
    }
    location ~ /index\.html$ {
        internal; # only accessible via internal URI rewrite
        try_files $uri =404;
    }
    location ~ \.(js|css|jpe?g|png)$ {
        # serve the assets in a usual way
        try_files $uri =404;
    }
}

Making that location ~ /index\.html$ { ... } an internal, you prevent direct access to your index.html files via requests like http://localhost/a/index.html (an HTTP 404 Not Found will be returned instead). However requests like http://localhost/a/ remains workable because of the internal URI rewrite by index directive together with try_files $uri/ =404 one.

Pooya Estakhri avatar
cn flag
This answer needs many more upvotes.
Wild Pottok avatar
in flag
Thanks for this great explanation. Minor point: you mention, in the case for ```curl http://localhost/c/```, that it returns a 403 code; why not a 404?
lafleur avatar
in flag
Great explanation! My thoughts were: $uri examines URI as it is and $uri/ examines URI as a directory looking out for an index file. For http://localhost/a `try_files $uri /file.html = 404; ` I get file.html. Good for now! For http://localhost/a `try_files $uri/ /file.html = 404; ` I get file.html too. Why? I was expecting the index.html. Furthermore, `try_files $uri $uri/ /file.html = 404;` will get me the index.html.
Ivan Shatsky avatar
gr flag
Good question! See the update to the answer.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.