网络技术

Nginx location 匹配规则与 rewrite 实战:从入门到生产配置

✎ -- 字 🕐 -- 分钟
字号

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 Step 1:查找精确匹配 = Step 2:查找最长普通前缀 / ^~ Step 3:若 ^~ 命中则使用它 Step 4:按顺序匹配 ~ / ~* 确定最终 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 配置。