Nginx location 匹配与 rewrite 重写规则实战:优先级排序、配置示例与排错指南


前言:为什么一定要搞懂 location 与 rewrite

很多站长在给 VPS 配置 Nginx 时,最容易踩坑的就是 location 匹配规则和 rewrite 重写指令。明明照着网上抄了一段配置,结果访问路径不是 404 就是 403,甚至出现无限重定向循环。这两者的本质都是对 URI 做处理,但一个负责「选块执行」,一个负责「改写地址」,规则互相叠加后非常容易混乱。本文从匹配优先级、常用语法、真实场景示例和排错思路四个维度,把这两个核心模块彻底讲透。

一、location 匹配规则与优先级

location 用于在 Nginx 配置中按 URI 前缀或正则选择配置块。理解优先级是排错的第一步,Nginx 官方给出的匹配顺序如下:

1. 精确匹配   location = /path
2. 前缀最长匹配 location ^~ /path
3. 正则匹配(按配置文件书写顺序,命中的第一条生效)
   location ~  /path     (区分大小写)
   location ~* /path     (不区分大小写)
4. 普通前缀匹配 location /path
5. 兜底匹配   location /

一个常见的误解是「配置写在前面的优先级更高」,实际上正则之间才看书写顺序,前缀匹配永远遵循「最长匹配原则」。我们用一个例子验证:

server {
    listen 80;
    server_name example.com;

    location = / {
        return 200 "exact root\n";
    }

    location /images/ {
        return 200 "prefix images\n";
    }

    location ^~ /images/logo/ {
        return 200 "longest prefix logo\n";
    }

    location ~* \.(png|jpg)$ {
        return 200 "regex image\n";
    }
}

访问 /images/logo/a.png 时,命中顺序是:^~ /images/logo/ 前缀最长,且带 ^~ 的普通前缀一旦匹配成功就不会再去尝试正则,因此返回 longest prefix logo。如果把 ^~ 去掉,则正则 ~* \.(png|jpg)$ 会覆盖掉最长前缀,返回 regex image。这就是「^~ 能截断正则检查」的关键点。

二、location 常用修饰符对比

修饰符匹配方式是否检查正则典型用途
=精确匹配否首页、固定路径,性能最高
^~前缀匹配否静态资源目录,防止被正则干扰
~正则(区分大小写)是按文件类型、文件名规则分流
~*正则(不区分大小写)是兼容 .JPG/.jpg 等大小写混用
无修饰符前缀匹配是普通目录级配置

实际配置中强烈建议:能用前缀解决的就不要用正则,因为前缀匹配速度远快于正则匹配,且逻辑更直观。以下是一份静态资源站点的推荐写法:

location ^~ /assets/ {
    alias /var/www/static/;
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
    root /var/www/html;
    expires 7d;
    add_header Cache-Control "public";
    try_files $uri =404;
}

三、rewrite 重写指令语法

rewrite 属于 ngx_http_rewrite_module,语法为:

rewrite 正则  替换地址  [标记];

标记共有四种,含义如下:

last    停止处理 rewrite 指令,重新用新 URI 匹配 location(内部跳转,URL 不变)
break   停止处理 rewrite 指令,直接按新 URI 在当前 location 内继续执行
redirect   返回 302 临时重定向(仅当替换地址以 http:// 或 https:// 开头时生效)
permanent  返回 301 永久重定向

last 与 break 是最容易混淆的一对。简单记忆:last 是「重写后重新走一遍匹配流程」,break 是「重写后停在本 location 就地执行」。看下面的对比:

# 场景 A:使用 last
location /old/ {
    rewrite ^/old/(.*)$ /new/$1 last;
}

location /new/ {
    return 200 "new block";
}

# 场景 B:使用 break
location /old/ {
    rewrite ^/old/(.*)$ /new/$1 break;
    root /var/www/html;   # 不会重新匹配 /new/,URI 停留在 /new/xxx
    try_files $uri =404;
}

场景 A 中访问 /old/a.html 会内部跳转到 /new/a.html 并重新匹配 location /new/,浏览器地址栏不变。场景 B 中 URI 被改成 /new/a.html 但不会重新匹配,直接在当前 location 里用 try_files 找文件。若想对外可见地改变地址,必须使用 redirect 或 permanent:

# 把 http 强制跳转 https 并保留路径
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

# 整站旧路径 301 迁移
rewrite ^/article/([0-9]+)\.html$ /posts/$1 permanent;

四、if 与 rewrite 的坑:尽量用 try_files 替代

Nginx 官方文档明确警告:if 指令在 location 上下文中容易产生不可预期的行为(著名的 if is evil)。比如下面这种写法在 Nginx 旧版本中会导致 404:

# 反例:不要这样写
location / {
    if (-f $request_filename) {
        break;
    }
    if (-d $request_filename) {
        break;
    }
    rewrite ^(.+)$ /index.php?q=$1 last;
}

推荐用 try_files 优雅替代,这也是 WordPress、Typecho 等伪静态配置的标准写法:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}

配合 if 唯一可以安全使用的场景是判断 http_user_agent 做简单防爬:

location / {
    if ($http_user_agent ~* "AhrefsBot|MJ12bot|SemrushBot") {
        return 403;
    }
    try_files $uri $uri/ /index.php?$query_string;
}

五、真实场景实战:伪静态、移动端跳转与协议跳转

场景 1:Typecho 伪静态

location / {
    if (!-e $request_filename) {
        rewrite ^(.*)$ /index.php$1 last;
    }
}

上面是网上的常见写法,但更推荐直接用 try_files 版本:

location / {
    try_files $uri $uri/ /index.php?$args;
}

场景 2:移动端与 PC 端分流跳转

server {
    listen 80;
    server_name www.example.com;

    # 手机浏览器访问 PC 页面时 302 跳转到移动站
    if ($http_user_agent ~* "(Mobile|Android|iPhone|iPad)") {
        rewrite ^/(.*)$ https://m.example.com/$1 redirect;
    }

    location / {
        root /var/www/pc;
        index index.html;
    }
}

场景 3:首页强制 HTTPS 并携带 HSTS

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/nginx/ssl/example.com.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        root /var/www/html;
        index index.html;
    }
}

六、常见报错与排错方法

问题 1:页面出现 404,但文件明明存在
大概率是 root 与 alias 用混了。root 会把 location 的 URI 拼到根目录后面,alias 则直接用别名替换前缀:

# root:访问 /images/a.png → /var/www/static/images/a.png
location /images/ {
    root /var/www/static;
}

# alias:访问 /images/a.png → /var/www/static/a.png
location /images/ {
    alias /var/www/static/;
}

排查命令:先确认实际磁盘路径,再用 nginx -t 校验语法,最后看 error.log:

grep -E "open\(\).*failed|No such file" /var/log/nginx/error.log | tail -20

问题 2:无限 301 重定向(ERR_TOO_MANY_REDIRECTS)
通常是 rewrite 规则把 / 也重写了,形成 A→B→A 的循环。先在 curl 里看响应头:

curl -sI http://example.com/ | grep -i location
curl -sI https://example.com/ | grep -i location

再检查是否有两条相互冲突的规则(如同时配置了 http 跳 https 和 https 跳 http)。一个常见诱因是 CDN 或反代回源到 80 端口,导致回源请求也被强制跳转,正确做法是在反代层只信任特定来源:

# 只在非回源场景强制跳转
map $http_x_forwarded_proto $scheme_ok {
    default 0;
    https   1;
}

server {
    listen 80;
    if ($scheme_ok = 0) {
        return 301 https://$host$request_uri;
    }
}

问题 3:rewrite 之后静态资源 404
检查标记是 last 还是 break,并确认新 URI 在目标 location 里有对应的 root/alias。开启调试日志能直接看到内部跳转过程:

# 临时在 http 块中开启
error_log /var/log/nginx/error.log debug;

然后重新请求,观察日志中的 internal redirect 与 rewritten data 字段,能清晰看到每一条 rewrite 的执行轨迹。

问题 4:正则 location 意外拦截了动态请求
正则一旦命中就覆盖所有前缀匹配(除非 ^~)。如果 PHP 文件被静态规则吞掉,加一条更靠前的精确正则:

location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    include fastcgi_params;
}

正则按书写顺序匹配,把更具体的规则写在前面即可。

七、性能与安全建议

  • 能用前缀 location 就别用正则,正则越多 CPU 开销越大。
  • 根路径 location = / 做精确匹配,避免 location / 兜底误伤。
  • 对外跳转使用 permanent/redirect,内部路由用 last/break,职责要分清。
  • rewrite 尽量写在 server 级,减少 location 内的重复匹配。
  • 生产环境上线前先 nginx -t 校验并 nginx -s reload 平滑加载。
nginx -t && nginx -s reload

八、总结

location 与 rewrite 是 Nginx 配置里最容易出错也最值得掌握的部分。记住四句话:精确匹配优先、最长前缀兜底、正则按序命中、^~ 阻断正则;rewrite 用 last 重跑匹配、break 就地执行、redirect 外跳、permanent 永跳。遇到问题时先用 curl -I 看响应头,再开 debug 日志跟内部跳转轨迹,最后用 nginx -t 验证语法,绝大多数疑难杂症都能快速定位。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Nginx 限流与防 CC 攻击实战:limit_req、limit_conn 配置与封禁策略全解析

优刻得UCloud:国内中立科创板云厂商,兼顾企业合规与开发者轻量化上云

评 论
更换验证码