Nginx 作为高性能反向代理与 Web 服务器的代表,location 块几乎是每份配置文件的"基本功"。然而很多开发者在生产环境中都遇到过这样的困惑:明明写了 location /api,请求却落到了 location /;配置了一条 rewrite,结果浏览器陷入 302 死循环;静态文件加了版本号后缓存策略失效……这些问题的根源,往往是对 location 匹配优先级与 rewrite 执行机制理解不够深入。本文将从基础语法、优先级判定、rewrite 指令到实战案例,系统梳理 Nginx 中这套"看似简单、实则精妙"的规则。
1. 为什么 location 配置总是踩坑
location 的本质是"按 URL 路径进行路由选择"。Nginx 在收到请求后,会遍历所有 location 块,依据匹配规则选出"最合适"的一个来处理请求。这个"最合适"不是简单的最长匹配,而是由修饰符、正则表达式、书写顺序共同决定。一旦规则冲突,轻则路由错误,重则引发安全漏洞。
常见的踩坑场景包括:
- 前缀匹配被精确匹配覆盖:以为
location /api会处理所有 /api 请求,但location = /api会截走精确路径。 - 正则匹配"抢"了前缀匹配:
~* \.(jpg|png)$写在前面,导致所有图片请求绕过了缓存优化配置。 - rewrite flag 用错:该用 permanent 却用了 redirect,SEO 权重没有正确传递;该用 last 却用了 break,后续 location 匹配被打断。
- location 内部 alias 与 root 混淆:路径拼接逻辑不同,导致 404 或目录遍历风险。
理解这些坑的前提,是先搞清楚 location 的五种语法形式。
2. location 的五种匹配语法
Nginx 的 location 支持五种修饰符,分别为:空(无修饰符)、=、^~、~、~*。它们决定了一条 location 如何与请求 URI 进行匹配,以及匹配成功后是否继续查找其他 location。
| 修饰符 | 含义 | 匹配类型 | 命中后是否继续查找 |
|---|---|---|---|
| (空) | 普通前缀匹配 | 前缀 | 会继续查找更精确或正则匹配 |
| = | 精确匹配 | 完全相等 | 立即终止,不再查找 |
| ^~ | 前缀优先匹配 | 前缀 | 若当前匹配最长,则跳过正则匹配 |
| ~ | 区分大小写的正则匹配 | 正则 | 按书写顺序命中第一个后终止 |
| ~* | 不区分大小写的正则匹配 | 正则 | 按书写顺序命中第一个后终止 |
一个容易忽略的细节是:location / 会匹配所有请求,因为它是最短的前缀;而 location = / 只匹配根路径。两者的性能和语义完全不同。
3. location 匹配优先级:一张图说清楚
当一条请求到达时,Nginx 的 location 选择流程如下:
用一句话概括优先级:精确匹配 > 前缀最长匹配(^~ 可截断正则) > 正则匹配(按配置顺序) > 普通前缀最长匹配。这里的"前缀最长"指的是在所有前缀匹配(空和 ^~)中,与 URI 前缀重合部分最多的那个。
实战建议:
- 高频静态资源使用
location ^~ /static/,避免被后面的正则误伤。 - 精确路径如
location = /health使用=,减少不必要的正则扫描。 - 正则 location 尽量放在文件末尾,并加注释说明顺序意图,避免同事误调整。
4. rewrite 模块核心指令
4.1 rewrite 语法
rewrite 指令用于重写请求 URI,语法为 rewrite regex replacement [flag];。它作用于进入 location 后的请求,根据正则替换 URI,然后由 flag 决定下一步动作。
server {
listen 80;
server_name example.com;
location /old/ {
rewrite ^/old/(.*)$ /new/$1 permanent;
}
}
上面的配置会把 /old/page.html 重定向到 /new/page.html,并返回 301 状态码。
4.2 return 指令
如果只是做简单跳转或返回固定状态码,return 比 rewrite 更清晰高效:
location /deprecated {
return 410; # 返回 410 Gone
}
location = /old-home {
return 301 /home;
}
4.3 四种 flag 的区别
| flag | 行为 | 使用场景 |
|---|---|---|
| last | 重写后重新进行 location 匹配 | 内部路径映射,不暴露给客户端 |
| break | 重写后停止 rewrite 扫描,但继续在当前 location 内处理 | 同一 location 内多次 rewrite |
| redirect | 返回 302 临时重定向 | 短期 URL 变更、测试环境 |
| permanent | 返回 301 永久重定向 | SEO 权重迁移、旧站下线 |
注意:在 server 上下文使用 rewrite 时,last 和 break 都会直接在当前 server 内重新匹配 location;在 location 内部使用时,last 会触发新一轮匹配,而 break 不会。
5. 实战案例:从简单到复杂
5.1 静态资源版本化与缓存
前端构建后常生成带 hash 的资源文件,同时需要兼容旧路径请求:
location ~* ^/assets/.+\.(js|css|png|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
这条规则用 ~* 做不区分大小写的正则匹配,并设置长期缓存。注意:如果同时有 location ^~ /assets/,^~ 会优先命中,因此不要把缓存头放到互相冲突的位置。
5.2 API 与前端路由共存
现代单页应用通常需要把非 API 请求全部转发到 index.html:
location /api/ {
proxy_pass http://backend;
}
location / {
try_files $uri $uri/ /index.html;
}
这里 location /api/ 是普通前缀匹配,优先级低于正则,但只要没有更具体的正则与它冲突,请求就会正确进入后端。
5.3 旧站 URL 301 迁移
站点改版时,老文章链接需要永久跳转以保持 SEO 权重:
location ~ ^/blog/(\d{4})/(\d{2})/(.+)\.html$ {
rewrite ^ /posts/$3 permanent;
}
这里使用 permanent 返回 301,搜索引擎会把旧 URL 的权重传递到新 URL。
5.4 强制 HTTPS 与 www 规范化
将 HTTP 请求和裸域请求统一规范化:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
return 301 https://www.example.com$request_uri;
}
这种写法简单直接,避免在 443 server 里写复杂的 rewrite,可读性更好。
6. 常见陷阱与调试技巧
- 陷阱 1:proxy_pass 后 URI 拼接差异。
proxy_pass http://backend/(带斜杠)会去掉 location 匹配部分;proxy_pass http://backend(不带斜杠)会保留原始 URI。混用会导致 404。 - 陷阱 2:rewrite 循环。last 触发重新匹配后,如果新 URI 仍然匹配同一 location,可能形成循环。用 break 或调整正则避免。
- 陷阱 3:正则顺序依赖。多个 ~ 或 ~* 按配置顺序匹配,第一个命中的生效。不要把宽泛的正则写在具体的正则前面。
- 陷阱 4:alias 末尾斜杠。
alias /data/static/与alias /data/static在部分路径下行为不同,建议统一带斜杠并测试边界。
调试时,先用 nginx -t 检查配置语法,再用 nginx -s reload 平滑加载。遇到路由问题时,开启 rewrite 日志:
error_log /var/log/nginx/rewrite.log notice;
rewrite_log on;
通过日志可以看到请求实际进入的 location 与 rewrite 过程,是定位问题的利器。
7. 总结
Nginx 的 location 与 rewrite 是 Web 流量的"交通指挥系统"。精确匹配保证关键路径不走弯路,^~ 前缀匹配为静态资源保驾护航,正则匹配处理复杂模式,而 rewrite 则负责 URL 的规范化与迁移。只要牢记优先级顺序、flag 差异以及 proxy_pass 的 URI 拼接规则,就能避开大部分线上陷阱,写出既高效又可维护的 Nginx 配置。