改一行 location,某个静态目录突然 404,或者反代的路径莫名其妙少了一截——Nginx 的坑大多出在 location 匹配上。核心症结只有一个:location 的优先级由写法决定,和配置文件里的先后顺序无关。很多人按「从上往下」的心智模型写配置,规则一多就必然出事。这篇文章把匹配算法、四个高频坑和验证方法一次讲完,环境为 Ubuntu 24.04 官方仓库的 Nginx 1.24.0(nginx -v 可查),示例规则在其他版本行为一致。
一张表看懂优先级
location 一共五种写法,优先级从高到低:
| 写法 | 示例 | 匹配行为 | 优先级 |
|---|---|---|---|
= | location = /login | URI 完全一致才命中,命中即终止 | 1 |
^~ | location ^~ /static/ | 最长前缀命中后跳过正则 | 2 |
~ | location ~ \.php$ | 正则,区分大小写 | 3 |
~* | location ~* \.(jpg|png)$ | 正则,忽略大小写 | 3 |
无符号 / / | location /api/ | 普通前缀,取最长者 | 4 |
匹配算法按四步走:
- 先查所有精确匹配(
=),命中立即终止,正则和前缀都不再看; - 记录命中的最长前缀;如果这个前缀带
^~,直接用它的配置,正则阶段跳过; - 否则按配置文件里的书写顺序逐条试正则,第一条命中的生效;
- 正则全部落空,才回退到第 2 步记下的那个最长前缀。
只有正则这一层看书写顺序。前缀之间比的是长度,前缀和正则之间比的是类型,location 的优先级与配置文件里的位置基本无关。
^~ 拦截正则,不只是更长的前缀
^~ 最常见的误解是「更严格的前缀匹配」。它和普通前缀的匹配精度完全一样,唯一的区别是命中后跳过正则阶段。
典型受害场景:给 /static/ 写了普通前缀,另有一条 ~* \.(css|js)$ 的正则管缓存头。这时 /static/app.css 会被正则抢走,静态目录的配置整个失效。把 /static/ 的前缀加上 ^~,正则对这条路径下的所有请求一律失效,目录才真正归你管。
结论直接给:静态资源目录、附件路径这类确定归属的路径,一律用 ^~。正则留给自己确实需要模式匹配的场景。
正则与大小写:.jpg$ 管不住 .JPG
~ 区分大小写,~* 不区分。~ \.(jpg|png)$ 匹配不了 photo.JPG——请求会穿透这条规则,落到普通前缀或正则后面的规则上。行为表现是同一类文件一会儿走静态一会儿走后端,排障时极难定位。
另一处规范细节:正则里要么从 URI 开头匹配 ^,要么锚定结尾 $,裸写一段字符串会误伤所有包含该子串的路径。. 必须转义成 \.,否则它按正则的「任意字符」工作,\.(jpg|png)$ 写成 .(jpg|png)$ 会连 .jpgx 都放进缓存逻辑。
proxy_pass 尾斜杠决定路径去留
反代路径拼接是 location 的第二大坑,规则就一条:proxy_pass 带 URI 部分,替换 location 前缀;不带,原样透传。这条规则本身简单,翻车都出在两侧约定对不上。
location /api/ {
proxy_pass http://backend/; # 有斜杠:/api/user → http://backend/user
}
location /api/ {
proxy_pass http://backend; # 无斜杠:/api/user → http://backend/api/user
}
带斜杠等于告诉 Nginx「把 /api/ 这段前缀裁掉再转发」,不带则是整个 URI 原样交给后端。后端路由如果按 /api/xxx 设计,转发时裁前缀就会出现 404。生产环境最常见的翻车方式:后端是 Spring 的 @RequestMapping("/api/user"),Nginx 这边写了 proxy_pass http://backend/;,请求到达后端时 /api/ 已经被裁掉,路由必然对不上。
另一个约束:proxy_pass 带 URI 时,location 里不能再用正则。正则 location 下的转发只能写不带 URI 的形式,路径改写交给 rewrite 完成。
root 拼完整 URI,alias 替换前缀
两者都服务静态文件,路径算法不同:
| 对比项 | root | alias |
|---|---|---|
| 拼接方式 | root 路径 + 完整 URI | alias 路径替换 location 前缀 |
| location 前缀 | 保留 | 不保留 |
| 典型用途 | 目录结构和 URL 一致 | URL 路径和磁盘路径错位 |
| 正则 location | 直接可用 | 必须配合捕获组 $1 |
location /static/ {
root /var/www; # /static/app.css → /var/www/static/app.css
}
location /static/ {
alias /var/www/assets/; # /static/app.css → /var/www/assets/app.css
}
alias 的坑集中在斜杠上:location 写 /static/ 而 alias 写 /var/www/assets(结尾无斜杠),/static/app.css 拼出来的真实路径是 /var/www/assetsapp.css。两边要么都带要么都不带。正则 location 里的 alias 必须用捕获组显式映射路径,alias /var/www/$1; 配 ~ ^/static/(.*)$ 这种写法,漏了 $1 就是全量 404。
十分钟验证法
规则写完别靠猜,Nginx 自带的工具链足够定位:
# 语法与逻辑检查:rewrite 或 location 指令放错位置,-t 会直接报错
sudo nginx -t
# 请求打到哪个 location,看 access_log(默认日志格式含 upstream 字段的可加 $uri)
tail -f /var/log/nginx/access.log
# 最直接的手段:临时把 error_log 调到 notice,命中哪条正则、匹配到哪个 location 全部可见
error_log /var/log/nginx/error.log notice;
改配置前先备份,验证用 curl -I 打真实 URL 看响应头,比刷新浏览器快一个量级。
常见问题
Q:location = / 和 location / 同时存在会怎样?
location = / 只接住首页这一个请求,其余路径全部落到 location /。这是给首页单独配缓存或跳转的标准写法,两条并存不冲突。
Q:多个正则都命中,用哪条? 配置文件里书写在最前面的那条。正则是按顺序短路的第一命中制,想调整优先级就调整位置。
Q:^~ /static 和 ^~ /static/images/ 同时命中选哪个?
取最长前缀。前缀匹配永远比长度,与书写顺序无关。
Q:location 里能用 if 吗?
if 是 rewrite 模块的指令,语法合法,但「if is evil」——if 里写非 rewrite 类指令(proxy_pass、content handler)容易触发不可预期的行为,路径分流用 location 组合解决,别在 if 里塞逻辑。
Q:怎么确认某个 URL 最终走到了哪条规则?
error_log 调到 notice 后重载,请求一次,日志里会打印正则匹配与 location 命中的过程;或者临时给目标 location 加一条 add_header X-Hit "rule-a";,响应头直接给出答案。
匹配规则的核心是一张表加一段算法,四步流程记住「精确、前缀、正则、回退」四个词,剩下的问题都出在 proxy_pass 和 alias 的拼接细节上——这两处的规则各只有一句话,写配置时多看一眼尾斜杠,能省掉大部分线上事故。