Nginx try_files 详解

Last Modified: 2023/08/11

try_files 指令说明

Syntax:	try_files file ... uri;
try_files file ... =code;
Default: —
Context: server, location

try_files 后面跟一个或者多个文件名称,严格来说最后一个应该称之为 uri 而不是文件名称。(注:最后一个也可以使用等号加 http 状态码) 当用户访问的 uri 匹配到 try_files 对应的 location 时,会从前往后依次检查每个文件(文件的目录由 root 或 alias 指定)是否存在,一旦文件存在则返回该文件,处理结束。 如果前面的文件名称都没匹配上,到达了最后一个 uri,则发起一个内部重定向跳转到 uri 指定的路径。

为了方便说明,我们假设一个配置如下:

location /static {
    root /home/saltyfish/static;
    try_files /index1.html /index2.html /index3.html /page/fallback;
}

假设用户访问的 uri 为 /static/hello,该 uri 匹配上了 /static location,因此会去 root 指定的目录下依次查找 index1.html、index2.html 和 index3.html,如果在 root 指定的目录下这个三个文件都不存在,那么内部重定向到 /page/fallback。

什么是内部重定向到 /page/fallback?即 Nginx 会使用 /page/fallback 重新匹配 location,并使用匹配上的 location 来处理用户的请求。

假设 nginx.conf 还有一个 location 配置如下:

location /page/fallback {
    root /home/saltyfish/static;
    try_files /fallback.html =422;
}

该 location 显然可以匹配 /page/fallback,因此首先会检查 /home/saltyfish/static/fallback.html 是否存在,如果存在则返回该文件,如果不存在则响应 422。

为什么称之为内部重定向呢?我个人的理解是重定向过程发生在 Nginx 服务器内部,浏览器中的地址始终保持为 /static/hello,假如浏览器中的地址变为了 /page/fallback,那么就是外部重定向了。

上面我们假设了 index1.html、index2.html 和 index3.html 文件均不存在,但假如 index2.html 存在,此时户访问任意 /static/** 路径都会被 /static location 匹配上,都会返回 index2.html。

有时候这不是我们所希望的,我们希望用户访问 /static/a,返回 root 指定的目录(以下简称 R)的 a 文件,用户访问 /static/b,返回 R 目录下的 b 文件。这很简单,我们只要去掉 try_files 指令即可。

location /static {
  root /home/saltyfish;
}

如果希望保留 try_files,则可以使用下面的配置方法:

location /static {
  root /home/saltyfish;
  try_files $uri $uri/ /page/fallback;
}

try_files 中的 $uri 是个变量,这个变量的含义就是单词本身的意思,变量的值为用户请求的 uri。假设用户在浏览器中访问了 http://a.com/static/hello,那么 用户请求的 uri 为 /static/hello,将该值代入上面的 try_files,得到:

try_files /static/hello /staic/hello/ /page/fallback;

注意:需要特别说明,不能简单的将用户请求的 uri 代入 try_files,这里只是为了方便大家理解,实际上 try_files 后面的 $uri 始终用来匹配文件是否存在,$uri/ 始终用来检查目录是否存在。

根据 try_files 规则,会去 /home/saltyfish 目录下依次检查 /static/hello/static/hello/,如果 /static/hello 文件存在,那么就返回 hello 文件,请求处理结束。

但是如果 hello 文件不存在,则会继续检查 /home/saltyfish 目录下的 /static/hello/ 这个目录是否存在,如果目录存在,浏览器会被重定向到 http://a.com/static/hello/,注意 hello 后面多了个斜杠。

浏览器重定向后,用户请求的 uri 变为了 /static/hello/,依然能够匹配上 /static location,如果只是简单的将用户请求的 uri 代入 try_files,会得到:

try_files /static/hello/ /staic/hello// /page/fallback;

如果是简单代入,那么 try_files 中的 $uri/ 将会变成 /static/hello//。实际情况是用户被重定向到 http://a.com/static/hello/ 之后,在 Nginx 看来用户此时访问的是个目录,而 $uri/ 正是用来匹配目录的,如果目录 /home/saltyfish/static/hello 存在,且目录下没有 index.html,便会返回 403。error.log 中提示的错误如下:

2023/08/06 10:22:18 [error] 36255#0: *266 directory index of "/home/saltyfish/static/hello/" is forbidden, client: 127.0.0.1, server: localhost, request: "GET /static/hello/ HTTP/1.1", host: "localhost"

为了修正这个错误,我们有两种选择:

  • 如果我们想开放 /static/hello 目录给用户访问,那么可以开启 autoindex on。
  • 如果我们不想开放 /static/hello 给用户访问,可以在 /static/hello 目录下面放个 index.html,这是因为 index 指令的默认值是 index index.html,当用户访问 /static/hello 目录时会将 index.html 发送给用户。

开放目录的配置方法如下:

location /static {
  root /home/saltyfish;
  autoindex on;
  try_files $uri $uri/ /page/fallback;
}

注意:开放目录是敏感操作,请确保不会泄露敏感资源。

写在最后

文章开头我们给出一段配置如下:

location /static {
    root /home/saltyfish/static;
    try_files /index1.html /index2.html /index3.html /page/fallback;
}

如果将上面的配置改成下面这样可以吗?

location /static {
    root /home/saltyfish/static/;
    try_files index1.html index2.html index3.html /page/fallback;
}

如果 /home/saltyfish/static/ 目录下存在 index2.html,那么当用户访问 /static/xxx 时,能够匹配上 index2.html 并返回给用户吗?

答案是不能,大家可以自行尝试。Nginx 的配置有时候多一个 / 和 少一个 / 会产生截然不同的结果,所以在配置的时候需要谨慎选择。

有问题吗?点此反馈!

温馨提示:反馈需要登录