1. 为什么说rewrite是Nginx里最容易被低估的功能
接触Nginx越久,越发现rewrite模块是个“用好了能救命,用不好会埋雷”的东西。刚入行那会儿,我觉得rewrite不就是个302跳转嘛,无非是把旧链接转到新链接。后来被线上事故教育了几次,才真正意识到它远不只是“跳转”这么简单——它是Nginx对请求URI做“重写”的完整机制,是网关层做流量治理、URL规范化、系统迁移、伪静态适配的核心手段。
举个最直观的例子:你有一个搜索接口,对外URL是 /search?keyword=nginx,但业务上希望用户访问 /list-nginx.html 也能命中同一个接口。这种“把静态化的URL翻译成动态参数请求”的能力,就是rewrite的典型应用。再比如,网站从http全面切到https时,老链接、收藏夹、外站引用还带着 http:// 前缀,如果直接在应用层做跳转,每个服务都要改一遍,而用Nginx在入口层统一rewrite,一行规则就能覆盖所有上游服务,省时省力还不容易漏。
这篇文章我会从应用场景、语法细节、实操案例、踩坑经验四个维度展开,适合三类读者:一是刚接触Nginx、搞不清rewrite和location谁先执行的新手;二是已经在用rewrite但总遇到“规则不生效”“循环重定向”“参数丢失”等问题的初中级运维和开发;三是正在做系统迁移、URL规范化、网站重构,需要在入口层做统一请求治理的团队。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 重写功能的核心机制拆解
2.1 rewrite的本质:把请求URI改写成另一个URI
rewrite模块做的事情用一句话说透:根据你定义的规则,改写Nginx当前请求的URI。这个“改写”可能是在内部重新匹配location并处理,也可能是直接向客户端返回一个重定向响应(301、302等),让浏览器重新发起新的请求。
它和普通Web框架里的路由机制有一个很重要的区别:rewrite发生在HTTP请求正式进入location处理逻辑之前或之后,依赖Nginx本身的正则引擎和匹配顺序。你可以用精确字符串匹配,也可以用正则去匹配一类URL。正因为它在Nginx内部完成,所以性能开销远小于回源到应用层再让PHP/Java框架去“转发”。
需要注意一个关键点:rewrite只修改URI部分,不修改请求方法(POST/PUT等),也不影响请求体。如果你想根据条件修改请求头、Cookie这些,得配合 if、set 和 proxy_set_header 一起做,那是另一个话题。
2.2 rewrite和location的“谁先谁后”问题
这一点几乎每个学Nginx的人都会绕晕,我直接给结论:
- 如果在
server块里写rewrite,它会在当前server内的location匹配之前执行; - 如果在
location块里写rewrite,它会在命中这个location、处理完后续指令之前执行; - rewrite执行后,如果URI发生了变化,Nginx会基于新的URI重新进行location匹配,除非你用了
break标志让它在当前location内终结。
这个“重新匹配”的行为非常关键,也是很多“不生效”问题的根源。举例来说:
nginx复制server {
listen 80;
server_name example.com;
rewrite ^/old/(.*)$ /new/$1;
location /new/ {
return 200 "hit new location";
}
}
请求 /old/123 时,server层的rewrite先把URI改成 /new/123,然后Nginx重新做location匹配,命中 /new/,返回“hit new location”。如果这里不加 last 或 break 标志(后面会细讲),它的默认行为就是“继续匹配后续规则并最终会重新匹配location”。
2.3 rewrite的四个标志位到底怎么选
rewrite指令的完整语法是:
nginx复制rewrite regex replacement [flag];
flag一共有四种:last、break、redirect、permanent。很多教程喜欢背定义,但我觉得还是用场景讲最清楚。
last:停止执行当前这一轮rewrite指令的匹配,用替换后的URI重新发起location匹配。适合在server层写规则、希望把请求交给另一个location处理的场景。break:停止执行当前这一轮rewrite指令的匹配,并且不再重新做location匹配,就在当前location内继续执行后面其他指令(比如proxy_pass)。适合在location内部做精确定向、不想再跳来跳去的场景。redirect:返回302临时重定向,浏览器地址栏会变化,适合临时迁移、维护页面。permanent:返回301永久重定向,适合域名变更、HTTPS强制跳转等永久性调整。
用一张表总结它们的行为区别:
| 标志 | 是否停止当前rewrite匹配 | 是否重新匹配location | 客户端是否感知 |
|---|---|---|---|
| last | 是 | 是 | 否(内部转发) |
| break | 是 | 否 | 否(内部转发) |
| redirect | 是 | 否 | 是(302) |
| permanent | 是 | 否 | 是(301) |
2.4 关于if指令的兼容性提醒
rewrite常和 if 搭配使用,比如判断UA、判断文件是否存在、判断协议等。但Nginx官方文档对 if 是“尽量避免使用”的态度,因为它和 location 的交互在某些场景下会产生意想不到的行为。
我的建议是:if 里只做rewrite和return这
