location 匹配规则与常见坑

「配置明明写了,为什么没生效」——Nginx 里九成的这类问题都出在 location 的匹配规则上。四类修饰符有自己的优先级,正则按书写顺序短路,^~ 只在它是最长前缀时才起作用,rootalias 在 location 里的拼接方式还完全不同。本章把匹配算法拆开讲,并给一份排查清单。

要解决的问题

  • 同一路径写了多个 location,到底哪一个生效?
  • location /static/location ~ \.png$ 同时存在,图片请求走哪个?
  • 为什么 alias 明明指向了正确目录,还是 404 或多了一层目录?
  • 正则里的捕获组($1)怎么用进 proxy_passrewrite
  • try_files 与 location 怎么配合,SPA 回退和 PHP 安全防护怎么写才不出错?
  • rewritereturn 到底该选哪个?

匹配优先级

Nginx 的匹配不是「按书写顺序从上到下」,而是分两轮:

顺序修饰符形式规则
=location = /healthz精确匹配,命中即结束,不再看任何其他规则
^~location ^~ /static/前缀匹配,且必须是最长前缀才生效;命中后跳过正则
~ / ~*location ~* \.(png|jpg)$正则匹配,按配置文件出现顺序逐个试,第一个命中即用
location /api/普通前缀,记录最长匹配,先留着不立即生效
location /兜底

完整算法是:先找 = 精确匹配;再找出最长的普通前缀匹配,如果它带 ^~ 就直接用它;否则按顺序试正则,命中就用正则;正则全不命中,才用刚才记住的最长前缀。所以 ^~ 不是「比正则优先」的通用规则,只有它同时是最长前缀时才优先。

root 与 alias 在 location 里的差异

维度rootalias
拼接方式root 值 + 完整 URIalias替换掉 location 前缀
尾斜杠不敏感,但别写尾斜杠必须与 location 前缀一致,否则路径错层
能否用在正则 location可以可以,且必须配捕获组才能定位到文件
try_files 配合行为稳定历史上存在怪异行为,优先用 root
# root:路径 = /srv/www + 完整 URI
location /assets/ { root /srv/www; }              # /assets/a.png → /srv/www/assets/a.png

# alias:location 前缀被替换,两边斜杠必须一致
location /assets/ { alias /srv/media/; }          # 正确 → /srv/media/a.png
location /assets/ { alias /srv/media; }           # 错误 → /srv/mediaa.png

# 正则 location 里的 alias:用捕获组拼出文件名
location ~* ^/img/(.+\.(?:png|jpe?g|webp))$ { alias /srv/media/$1; }

正则捕获组与 rewrite

# 1) 正则 location + return:把 $1 拼进新地址
location ~ ^/old/(.*)$ { return 301 /new/$1; }

# 2) 正则 location + proxy_pass:直接写 URI 会报错,用变量才合法
location ~ ^/api/v(\d+)/(.*)$ {
    proxy_pass http://127.0.0.1:8080/v$1/$2;      # 含变量时,URI 整体替换原始请求路径
}
location ~ ^/api/ {
    proxy_pass http://127.0.0.1:8080;             # 不带 URI:路径原样透传,最省心
}

# 3) rewrite:改完路径后重新走 location 匹配(去掉 break 时)
location /blog/ { rewrite ^/blog/(.*)$ /articles/$1 last; }
location /articles/ { proxy_pass http://127.0.0.1:8080; }

rewrite 的四种 flag 要分清:

flag作用是否重新匹配 location
last用新 URI 重新走一遍 location 匹配
break停止 rewrite,继续在当前 location 内处理
redirect返回 302 临时跳转否(客户端重新请求)
permanent返回 301 永久跳转

permanent 会被浏览器长期缓存,改错了用户几个月都跳不回来,临时调整一律用 redirect

try_files 与 location 的分工

server {
    root /srv/www/site;

    location / { try_files $uri $uri/ /index.html; }        # SPA 回退,前端路由接管

    location /api/ { proxy_pass http://app_backend; }        # 接口走反代,不被 / 吃掉

    location ~ \.php$ { try_files $uri =404; }               # 关键:文件不存在直接 404

    location /files/ { try_files $uri $uri/ =404; }          # 纯静态目录
}

location ~ \.php$ { try_files $uri =404; } 是必须写的:没有它,任何 /uploads/evil.php 这类不存在的路径都会被交给解释器执行,属于典型的安全漏洞。try_files 里的 /index.html 前面有 / 表示内部重定向,会重新走 location /index.html 的规则,所以给 HTML 加的 no-cache 头依然生效。

「location 没生效」排查清单

# 1) 看合并后的最终配置(含所有 include),确认哪条真的生效了
nginx -T | grep -n 'location' -A6
# 2) 确认请求落在哪个 server 块(server_name 不匹配会落到 default_server)
nginx -T | grep -n 'server_name'
# 3) 打开 debug 日志看匹配过程(需 debug 版本或 error_log debug)
sudo tail -100 /var/log/nginx/error.log
# 4) 用带查询串的方式绕开浏览器缓存做实测
curl -sI 'https://example.com/static/a.png?v=1' | head -3

按概率排序的原因:

现象常见原因确认方法
完全不生效配置文件没被 include;改的是 sites-available 而生效的是 sites-enabled 软链nginx -T 里搜不搜得到这段
配了两个 location /nginx -tduplicate location校验输出直接看
正则 location 抢走了请求正则按顺序短路,先写的先赢nginx -T 看书写顺序
^~ 没起作用它旁边有更长的普通前缀把两条前缀长度对一遍
落到了别的站点server_name 没匹配上,用了默认 servernginx -Tdefault_server
改了但没生效只改了文件没 reloadnginx -t && systemctl reload nginx

常见坑

现象做法
以为书写顺序决定优先级精确匹配反而被写在后面,仍然生效记住「精确 > ^~ 最长前缀 > 正则顺序 > 最长普通前缀 > /
alias 与 location 尾斜杠不一致404 或多一层目录两边要么都带 /,要么都不带
SPA 回退写在 location /,接口被吃掉接口返回 HTML/api/ 单独 location,且写在前面
rewrite ... permanent 用于临时跳转改不回来,用户被缓存锁死临时跳转用 redirect
try_files 少了 =404不存在的脚本被交给解释器动态脚本 location 必加 try_files $uri =404;
改了配置没校验语法错误只有 reload 时才暴露固定 nginx -t && systemctl reload nginx

小结:location 的优先级是「精确匹配 > 最长的 ^~ 前缀 > 按顺序的第一个正则 > 最长的普通前缀 > /」,^~ 只有在它同时是最长前缀时才跳过正则;root 是拼接完整 URI、alias 是替换 location 前缀,尾斜杠必须两边一致;路径类问题不要去猜,用 nginx -T 看合并后的真实配置、用 curl -I 看真实结果,两分钟就能定位。

笔记加载中…