Nginx的rewrite模块负责在请求处理阶段对URI进行改写,常用于伪静态处理、旧路径迁移、前端路由回退等场景。它强大但容错率很低,一条规则的位置偏差或标志位选错,都可能让整个站点出现404、参数丢失甚至无限重定向。本文不会只罗列现成规则,而是从机制层面说明如何保证重写精准无误,并提供可落地的排错流程。

一、先把rewrite的执行顺序和flag机制搞清楚
Nginx的rewrite指令属于ngx_http_rewrite_module模块,可以在server和location两个上下文中使用。无论是哪种上下文,同层级内的rewrite规则都会按照书写顺序从上到下依次执行。每一条rewrite规则若匹配成功,就会用替换后的新URI继续参与后续rewrite规则的判断,除非遇到break或last等终止标志。
这里需要特别区分last和break。last表示结束当前一轮rewrite模块的指令执行,然后根据新的URI重新进行location匹配;break则表示立即停止当前上下文内所有rewrite模块指令,但不会重新搜索location。如果当前指令位于location内部,break会直接进入该location后续的内容处理阶段。redirect和permanent则相对直观,分别返回302和301状态码,并停止后续处理。
举一个容易混淆的例子:假设server级别有一条规则rewrite ^/api /backend last;,当请求/api/user到达后,该规则会把URI改为/backend/user,然后last触发重新搜索location。如果存在location /backend,请求会被正确接管。如果把last换成break,请求URI虽然变为/backend/user,但不会再重新搜索location,可能导致找不到对应资源。因此,理解flag的作用比背规则更重要。
server {
listen 80;
server_name ippipp.com;
rewrite ^/api/(.*)$ /backend/$1 last;
location /backend {
# 处理转发后的请求
}
}
二、四类高频错误:看似正常却埋雷
第一类是正则表达式没有做好锚定。例如rewrite ^/old /new permanent;本意是只迁移/old这个路径,但^/old会匹配到/old、/old-page、/old?x=1等任何以old开头的URI。正确的写法应该是rewrite ^/old$ /new permanent;,末尾加上美元符号表示URI结尾。对于需要保留子路径的情况,则要明确捕获组,例如rewrite ^/old/(.*)$ /new/$1 permanent;。
第二类是查询参数丢失。rewrite替换URI时,默认不会自动附加原始查询字符串。例如请求/search?page=2经过rewrite ^/search /query last;之后,/query后面不会自动带上page=2,需要写成rewrite ^/search /query? last;,其中问号表示把原始参数追加到新URI后。如果希望丢弃参数,就什么都不加;如果希望追加固定参数,可以写rewrite ^/search /query?from=old last;,但这样会丢失原参数,只有写/query?from=old&$args之类的方式才能合并。不过使用$args变量更直接,可以避免遗漏。
第三类是重写循环。典型表现是请求某个路径时浏览器反复跳转,最终报TOO_MANY_REDIRECTS。常见原因是两条规则互相改写,或者规则匹配了自己。例如在location /test中写rewrite ^/test /test last;,由于last会重新搜索location,又进入同一个location,再次匹配,形成循环。排查时可以用curl -I查看多次请求的Location头变化。
第四类是错误使用if配合rewrite。实践中容易写出这样的规则:if ($http_user_agent ~* "MSIE") { rewrite ^(.*)$ /ie.html break; }。但if与rewrite组合会引入更多隐性判断,推荐使用map指令或直接在location中定义不同规则,避免在一个if块里堆叠复杂逻辑。
# 错误示例:缺少锚定导致循环
location /test {
rewrite ^/test /test last;
}
# 正确示例:锚定完整路径并避免循环
location = /test {
return 301 /new-test;
}
三、用日志和curl把每一跳看清楚
大部分重写问题靠肉眼难以发现,需要打开rewrite日志。在http或server级别加入rewrite_log on;,并把error_log级别调整到notice,然后重载Nginx。此时每个请求的重写过程会输出到错误日志中,格式类似rewritten data: "/index.html", args: "", client: 127.0.0.1, server: ippipp.com, request: "GET /path HTTP/1.1", host: "ippipp.com"。通过日志可以确认替换后的URI是否符合预期,参数是否被正确携带。
命令行工具curl是另一个得力助手。使用curl -I http://ippipp.com/old-path可以只看响应头,重点观察HTTP状态码和Location字段。如果怀疑多次跳转,可以使用curl -L -v http://ippipp.com/old-path跟踪整个跳转链,-v会输出每一次请求和响应头。对于POST请求或需要携带Cookie的场景,可以使用curl -X POST -d "a=1" -v http://ippipp.com/test,确保重写没有把请求方法改变。
配置语法校验同样不能忽略。每次修改后先执行nginx -t,它会检查配置文件语法,包括rewrite规则中的正则是否合法。需要注意的是,nginx -t只能检测语法错误,无法发现逻辑错误,例如规则顺序不当或flag误用,因此日志验证必须跟上。
nginx -t systemctl reload nginx curl -I http://127.0.0.1/old-path curl -L -v http://127.0.0.1/old-path
四、能不用rewrite就不用,用就要可验证
在Nginx配置中,很多URL改写需求可以用更简单、更直观的指令完成。例如前端history路由模式下,单页应用需要把未知路径回退到index.html,官方推荐使用try_files而不是rewrite:location / { try_files $uri $uri/ /index.html; }。try_files按顺序检查文件或目录是否存在,不存在则内部重定向到最后一个参数,不会产生额外的重写日志,也不容易造成循环。类似的,纯路径跳转可以直接用return 301或return 302完成,语义清楚且性能更好。
如果确实需要rewrite,建议将规则集中在server级别,避免分散在多个location中导致难以追踪。每条规则应当加上完整的锚定和捕获组,并明确flag。对于旧路径迁移,优先使用permanent返回301,可以帮助搜索引擎更新索引;对于临时跳转或测试,使用redirect。内部改写则优先考虑last,并确认目标location能够正确处理新URI。
上线前可以建立一个小型测试清单:第一,列出所有需要重写的标准路径和边界路径;第二,为每条规则准备预期结果,包括状态码、Location和新URI;第三,在测试环境执行curl脚本,逐条比对响应头和日志;第四,检查是否误伤静态资源、API请求或带参数的URL。这套流程能大幅降低重写规则引发的线上事故。
Nginx rewriteURL重写配置错误修改时间:2026-08-30 13:28:14