nginx 的 location 到底怎么选?= 、^~ 、~ 、~* 和普通前缀的优先级?正则的顺序有影响吗?路径里的 // 和 %61 会怎样?macOS 和 Linux 上的大小写行为一样吗?这篇用 8 个 location 的矩阵逐项量出来。
请求路径 | 状态码 | 命中 --------------+-----+------------------------- /exact | 200 | A: = /exact /exact/ | 200 | H: prefix / /exact?x=1 | 200 | A: = /exact /a/x | 200 | B: prefix /a/ /a/b/x | 200 | C: prefix /a/b/ /a/b/c/d | 200 | G: regex ~ ^/a/b/c /a/b/cx | 200 | G: regex ~ ^/a/b/c /static/x | 200 | D: ^~ /static/ /static/x.php | 200 | D: ^~ /static/ /static/a.jpg | 200 | D: ^~ /static/ /x.php | 200 | E: regex ~ .php 结尾 /a/x.php | 200 | E: regex ~ .php 结尾 /a/x.png | 200 | F: regex ~* .jpg/.png 结尾 /pic.JPG | 200 | F: regex ~* .jpg/.png 结尾 /pic.jpg/ | 200 | H: prefix / /A/b/c | 200 | G: regex ~ ^/a/b/c /nothing | 200 | H: prefix / 几个值得注意的:/a/b/c/d 最长前缀是 C(/a/b/),但正则 G 匹配,正则胜过普通前缀;/a/x.png 同样是正则 F 胜过前缀 B;/static/x.php 命中 D 而不是 E,因为 ^~ 前缀一旦是最长前缀就不再试正则;/exact/ 不等于 /exact,落到 B 之外的 H;/A/b/c 在这台 macOS 上也命中了 G(~ 本应区分大小写,见第四节)
= 精确 > ^~ 最长前缀 > 正则(按顺序) > 普通最长前缀。
配置顺序 | /api/v1/users.json 命中 -------+---------------------- R1 写在前 | R1: ^/api/ R2 写在前 | R2: .json 结尾 配置 | /api/v1/users.json 命中 ---------------+---------------------- 前缀 /api/ 写在正则前 | R: .json 结尾 前缀 /api/ 写在正则后 | R: .json 结尾 前缀换成 ^~ /api/ | P: ^~ /api/ 普通前缀 location 的先后顺序无关紧要(按前缀长度比),但正则的先后顺序决定谁胜出;普通前缀和正则谁先写也不影响“正则胜过前缀”;想让某个前缀不被正则抢走,要用 ^~
正则按文件里的顺序试;普通前缀的先后无关紧要。
请求路径 | 状态码 | 命中 ----------------+-----+-------------- /a/x | 200 | B: prefix /a/ //a//x | 200 | B: prefix /a/ /%61/x | 200 | B: prefix /a/ /a/./x | 200 | B: prefix /a/ /a/b/../x | 200 | B: prefix /a/ /a/%2e%2e/exact | 200 | A: = /exact /a%2fx | 200 | B: prefix /a/ /exact%3Fx=1 | 200 | H: prefix / /exact?x=1 | 200 | A: = /exact nginx 在选 location 之前把 URI 规范化:合并连续的斜杠(merge_slashes 默认 on)、解码百分号(%61 = a)、去掉 . 和 .. 段;%2f(编码的斜杠)被解码成真正的斜杠还是留作字符,以及 %3F 是否被当成问号,都要看这一节的实际结果,不要凭想象
先合并斜杠、解码百分号、处理 . 和 .. 再匹配。
请求路径 | macOS · nginx 1.31.6 | Linux 容器 · nginx/1.27.5 --------------+--------------------------+------------------------- /EXACT | A: = /exact | H: prefix / /A/x | B: prefix /a/ | H: prefix / /A/B/x | C: prefix /a/b/ | H: prefix / /a/B/C/d | G: regex ~ ^/a/b/c | B: prefix /a/ /A/b/c | G: regex ~ ^/a/b/c | H: prefix / /pic.JPG | F: regex ~* .jpg/.png 结尾 | F: regex ~* .jpg/.png 结尾 /x.PHP | E: regex ~ .php 结尾 | H: prefix / /STATIC/x.php | D: ^~ /static/ | E: regex ~ .php 结尾 据我对 nginx 文档的记忆,在大小写不敏感的操作系统(macOS、Cygwin、Windows)上前缀 location 的匹配会忽略大小写(这一句我没有在这次实验里核对文档原文);实测这台 macOS 上连 ~ 正则也不区分大小写(/A/b/c 命中了 ~ ^/a/b/c),而 Linux 容器里 ~ 区分大小写。在 macOS 上开发、在 Linux 上部署的配置,路径大小写相关的路由行为可能不一致
macOS 上前缀和 ~ 都不区分大小写,Linux 上区分。