我们在配置 Nginx 时,经常会遇到 URL 重写的需求:比如把 /news/12345.html 重写到 /article/12345,或者根据请求来源做条件跳转。做到这些,离不开正则表达式捕获组。
这篇文章就来讲清楚:Nginx rewrite 里捕获组到底是什么、 到 怎么来的、什么时候用、哪些地方能用、哪些坑要避开。
一、先搞懂基本语法:rewrite 和捕获组的关系
Nginx 的 rewrite 指令长这样:
rewrite regex replacement [flag];
当 regex(正则表达式)和请求 URI 匹配上之后,URI 就会被 replacement 替换掉。
正则表达式里的圆括号 () 就是捕获组。每个括号里的内容会被"抓"出来,按顺序生成 、、……最多到 。
举个例子:
rewrite ^/article/(\d+)/(\w+)\.html$ /article/- permanent;
如果用户访问 /article/12345/tutorial.html,匹配之后:
=12345(第一个括号(\d+))=tutorial(第二个括号(\w+))
最终替换结果:/article/12345-tutorial,返回 301 永久重定向。
二、if 指令中的捕获组:条件判断的利器
除了 rewrite,Nginx 的 if 指令也支持正则匹配,而且同样能捕获分组。这在根据请求特征做条件判断时特别有用。
提取 Cookie 中的值
if (\ ~* "id=([^;]+)(?:;|\$)") {
set \ \;
}
这行配置的意思是:
- 用正则
id=([^;]+)从 Cookie 里提取id=后面的值(遇到分号为止) - 捕获到的内容赋给变量
\ - 再用
set \ \把\的值存到自定义变量\里
根据 User-Agent 做跳转
if (\ ~ MSIE) {
rewrite ^(.*)\$ /msie/\ break;
}
这里只有一个捕获组 (.*),匹配到的完整 URI 存入 \,然后重写到 /msie/ 路径下。
三、replacement 字符串的细节
捕获组拿到之后,在 replacement 里怎么写有几条规则要注意:
1. 直接引用变量即可
在 replacement 里直接写 \、\,Nginx 会自动替换成对应的捕获内容:
rewrite ^/old/(\d{4})/(\d{2})/(.*)\$ /new/\-\/\ last;
这个例子把 /old/2024/06/article 重写成 /new/2024-06/article,年月日格式重组就用到了多个捕获组。
2. 保留旧参数:加个问号结尾
默认情况下,replacement 之后原始的查询参数会追加到新 URL 后面。如果不想追加,在 replacement 最后加一个 ?:
rewrite ^/users/(.*)\$ /show?user=\ last;
这里 \ 是用户名,末尾的 ? 表示"不要附加旧参数"。如果不加 ?,请求 /users/tom?page=3 会变成 /show?user=tom&page=3(有时会造成参数混乱)。
3. 以 http:// 开头则中断重写发跳转
如果 replacement 字符串以 http://、https:// 或 \ 开头,Nginx 会直接返回 302 临时重定向,不再继续执行后续 rewrite 指令:
rewrite ^/legacy/(.*)\$ https://new.example.com/\ permanent;
这种情况下,如果 replacement 里还有 \,捕获组照样生效。
四、flag 对捕获组的影响
rewrite 指令末尾的 flag(last、break、redirect、permanent)决定了捕获组之后 Nginx 怎么继续处理:
- last:停止执行当前 server 块里的 rewrite 指令,用新 URI 重新搜索匹配 location,然后再走一遍 rewrite 流程(最多10轮)。捕获组在 replacement 中正常可用。
- break:停止执行当前 set ngx_http_rewrite_module 指令,继续在当前 location 里处理请求。捕获组同样可用。
- redirect:返回 302 重定向,replacement 里的捕获组正常替换后拼接成跳转 URL。
- permanent:返回 301 重定向,行为同上,只是状态码不同。
在 location 块里使用 last 时要小心:如果同一个 location 里有多条 rewrite 规则,last 会触发重新匹配 location,如果新 location 里还有 rewrite,容易触发"rewrite loop"(超过10次循环返回500错误)。这种情况下一般用 break 更安全。
五、括号嵌套:怎么数 、
捕获组的编号只看括号的位置顺序,不看嵌套层级。从左到右,第一个左括号对应 ,第二个左括号对应 ,以此类推。
rewrite ^/(\w+)/(\d+)/(\w+)\.html$ /\/\/\.html last;
请求 /article/12345/detail.html 匹配后:
=article=12345=detail
结果:/detail/12345/article.html。
六、常见错误与避坑
坑1:括号里有特殊字符要转义
正则里的小括号 () 是特殊字符。如果你要匹配字面意义上的左括号 (,需要转义写成 \(:
rewrite ^/product/Peanut\(Plain\)/(.*)\$ /product/peanut-plain/\ last;
坑2:} 和 ; 在正则里要加引号
如果正则表达式里包含 } 或 ;,整个正则要加引号包起来:
if (\ ~ "^/api/endpoint\?(.*)") {
set \ \;
}
不加引号 Nginx 会把 } 当作配置块结束符来处理,直接报错。
坑3: 在 location 块外可能不生效
捕获组变量 的作用域受限于 rewrite 指令执行上下文。在 server 级别和 location 级别都可以用,但 server 级别的 rewrite 里捕获了 之后,在后面的 location 块里直接用 可能不是你预期值——因为每个 rewrite 阶段都有独立的变量空间。
坑4:capture 组太多超过9个
Nginx 最多只支持到 (第九个捕获组),超过9个的捕获组无法用 这样的语法访问。如果确实需要更多分组,可以考虑把部分逻辑移到 Lua 脚本里处理。
七、实战案例:3个常用场景
案例1:伪静态 URL 重写
rewrite ^/news-(\d+)\.html\$ /article.php?id=\ last; rewrite ^/product/(\d+)\.html\$ /shop/product?id=\ last; rewrite ^/tag/([a-z0-9]+)\$ /topic?kw=\ last;
三个经典场景:数字ID重写、商品页重写、标签页重写,分别演示了用捕获组提取数字、纯数字路径和字母数字混合路径。
案例2:移动端适配跳转
if (\ ~* "Mobile|Android|iPhone") {
set \ "1";
}
if (\ = "1" && \ ~ ^/pc/(.*)\$) {
rewrite ^/pc/(.*)\$ /mobile/\ last;
}
先判断 UA,再从 URL 路径里用捕获组提取 PC 版路径,拼接成移动版路径后跳转。
案例3:批量旧 URL 迁移
rewrite ^/old/category/(\d+)/page(\d+)\.html\$
/new/category/\=\ last;
批量迁移时,旧 URL 的分类ID和页码用捕获组分别提取,加到新 URL 里,旧参数用 ? 结尾截断,避免参数残留。
八、总结
- Nginx rewrite 的捕获组用圆括号
()定义,从左到右依次生成到 - 捕获组在 rewrite 的 replacement 和 if 指令的正则匹配中都可以使用
- replacement 末尾加
?可以阻止旧参数追加 - flag 选择要根据 location 嵌套情况来定,避免 rewrite 循环
- 正则里有特殊字符(
()、}、;)要正确转义或加引号 - 超过9个捕获组的部分无法用
访问
掌握捕获组之后,你会发现 Nginx rewrite 的能力远不止"简单的301跳转"——它可以像程序一样灵活地处理 URL,是网站改版迁移、移动适配、参数重组的利器。
📚 相关文章推荐:
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论