☰
深入浅出Nginx URL重写:rewrite、return、try_files实战全解析
2026/10/14 7:40:09 网站建设 项目流程

上周有个朋友找我帮忙,说他的内容站要改版,域名不变,但目录结构全部推翻重来。旧文章的链接如果直接返回404,之前积累的搜索权重就全废了。我过去帮他配完Nginx的Rewrite规则,让旧链接全部301到新地址,顺手把这块知识重新梳理了一遍。因为我也发现,就算用了好几年Nginx的人,对rewrite、return、try_files三者的使用边界也经常搞混。更别提那两道面试必问题——last和break到底什么区别、URL重写和URL重定向是不是一回事——能当场讲清楚的人真的不多。

这篇文章不打算从"什么是Rewrite模块"这种百科式写法开始,而是从实际需求出发,把重写的原理、配置语法、常用flag的取舍、真实场景的规则设计、以及我在调试过程中踩过的坑一次性说透。不管你是刚接触Nginx的新手,还是已经在生产环境维护过一段时间的老手,这篇应该都有值得看的东西。文章会涉及具体配置片段和正则写法,建议你打开Nginx配置文件,边看边试,理解会深很多。

1. 从"URL翻译官"说起:Rewrite到底在解决什么问题

1.1 一个真实的旧链接迁移场景

先说开头那个改版案例。朋友的站点是文章类的,老结构是/post/123.html这种静态文件风格,新结构换成了/article/detail/123这种带语义的路径。他在后台做了数据库跳转逻辑,但搜索引擎和外部外链指向的还是老地址。这时候如果直接把Nginx配置改成新规则,访问老链接的人会看到404,搜索引擎爬虫也会把老页面的权重丢弃。

最简单的做法是让Nginx在收到/post/123.html时,直接告诉浏览器和爬虫:"这个页面已经搬家了,新地址是/article/detail/123,你们以后请用新地址访问。"这就是一个典型的URL重定向场景,用Nginx的rewrite配合301状态码就能搞定。

但rewrite能干的事远不止301跳转。它还可以在服务器内部重写URL —— 比如用户访问的是/user/9527,Nginx可以改写为/index.php?uid=9527后交给后端PHP处理,这个过程中浏览器地址栏里的URL完全不变。一个是对外重定向,一个是对内重写,虽然都是rewrite模块在干活,但背后的处理逻辑和用户感知完全不同。

1.2 重写、重定向和内部文件查找的关系

我在社群答疑的时候发现,很多人会把rewrite、try_files、return三个指令的职责搞混。其实它们的关系可以这样理解:

  • return是"直接回复",告诉客户端立刻返回某个状态码或跳转到某个URL,最干脆,性能也最好。
  • rewrite是"改写URL",改写后Nginx可能重新匹配location,也可能把改写后的URL继续交给后端处理,也可以对外发起重定向。
  • try_files是"按顺序试文件",如果试到的文件不存在,就内部重定向到最后一个URI,常用于伪静态和单入口框架。

它们经常搭配使用,但千万别把rewrite当成万能工具硬用。比如纯静态文件检查的场景,try_files比rewrite + if (-f)要简洁高效得多。这个后面我会专门展开。

1.3 先建立一个"三步走"的思维模型

这里先抛一个我自己总结的思维模型,后面所有配置理解都会用到:

拿到一条Rewrite需求,先问自己三个问题:

  1. 这个URL重写是在浏览器端可见的,还是仅在内部发生的?
  2. 匹配条件是固定路径、正则模式,还是需要依赖变量判断(比如UA、文件是否存在)?
  3. 改写之后,处理权是交给另一个location、交给后端,还是直接返回某个状态码?

这三个问题的答案组合,基本就能决定你该用哪条指令、配在哪个层次(server块还是location块)里。比如"浏览器端可见的老链接迁移"答案就是内部不可见、正则匹配、直接返回301 —— 于是你自然会选择rewrite ... permanent或return 301。而"伪静态URL转后端入口"则是内部可见、正则匹配、交给后端 —— 于是你会用rewrite ... last配合location转发,或者直接用try_files。

2. Nginx请求处理的"路线图":Rewrite在哪个环节起作用

2.1 从一条请求进入Nginx开始讲

一条HTTP请求进入Nginx后,并不是简单地从配置文件里找一段匹配规则就完事,而是要经过一整套定义好的处理阶段。Nginx官方把这套流程划分成11个阶段(post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content、log),其中和rewrite强相关的有四个。

我第一次看阶段表时觉得云里雾里,后来拿快递分拣做类比就通了:请求就像一个包裹,Nginx是分拣中心,每个阶段是一道流水线台位。包裹先被读取(post-read),然后可能被贴上"改写标签"(server-rewrite阶段,配置在server块里的rewrite指令在这里执行),接着按标签找到对应的处理窗口(find-config阶段确定location),找到之后还可能再被改写一次(rewrite阶段,location块内的rewrite在这里执行)。改写完之后,Nginx发现"这个包裹的标签变了,得重新分拣",于是在post-rewrite阶段重新走一遍location查找。

2.2 rewrite在server块和location块里的执行差异

理解了这个流程,很多怪问题就说得通了。比如你在一台服务器上配了多个server,每个server里都写了rewrite规则,这些规则是在server-rewrite阶段执行的,执行完才去匹配location。也就是说,server块里的rewrite可以在URL进入location匹配之前就改写它,改写后的URL会影响后续location的选择。

而location块内的rewrite规则,是在Nginx已经选定某个location之后才执行的。如果这个rewrite把URL改写了,Nginx会根据改写后的URL重新走一次location匹配(前提是你用了last)。这就是为什么在location里写rewrite时,last和break的行为差异特别明显——一个会重新找location,另一个直接在当前location"落地"处理。

2.3 用表格看清四个关键阶段的职责

我把和rewrite最相关的阶段整理成了一张表,方便对照:

阶段执行时机主要处理内容
server-rewrite匹配location之前server块中的rewrite指令、if指令中的rewrite/return
find-configserver-rewrite之后根据当前URI查找最佳匹配的location
rewrite选定location之后location块内的rewrite指令
post-rewrite上述所有改写完成后检查URL是否被改写,若有则重新走location匹配

值得注意的是,return指令的执行优先级高于rewrite。只要请求命中了带return的配置块,后面的rewrite规则就算写了也不会执行。这也是为什么很多老手建议"能用return说清楚的事情,就别用rewrite绕圈子"。

3. rewrite指令核心语法:正则、捕获组与四个flag的选择

3.1 基本语法长什么样

rewrite指令的标准格式是这样:

rewrite regex replacement [flag];

一条rewrite规则由三部分组成:用来匹配URI的正则表达式、要替换成的新URI、以及可选的flag(标记)。听起来简单,但真正想配得稳,正则功底和flag的理解缺一不可。

先看一个最小例子:

server { rewrite ^/post/(\d+)\.html$ /article/$1 permanent; }

这条规则把/post/123.html永久重定向到/article/123。^/post/(\d+)\.html$是正则,$1引用的是第一个捕获组(也就是\d+匹配到的数字),permanent表示返回301。

3.2 正则匹配的细节与常见误区

Nginx的rewrite正则使用的是PCRE(Perl Compatible Regular Expressions,Perl兼容正则表达式)语法。因为是兼容Perl的,所以很多在编程里常见的能力这里也都有:捕获组、非捕获组、字符类、量词、锚点等等。但有几个细节容易踩坑:

  • 默认只匹配URI,不包含参数。比如请求/test.php?id=1,rewrite规则匹配到的是/test.php,参数?id=1不会参与正则匹配。如果你想保留参数,需要借助变量或用$request_uri来获取完整原始URI。
  • ^和$锚点很重要。如果不写锚点,正则可能匹配到URL的中间部分,造成意料之外的替换。比如rewrite ^/images/(.*) /img/$1;和rewrite /images/ /img/;的行为差异就很大,后者的/myimages/也可能被匹配改写。
  • .需要转义。很多新手写.html时没注意,正则在PCRE中.是任意字符,所以^/post/123.html$实际上会把123ahtml也匹配进去。写URL匹配时,务必写成\.html。

3.3 四个flag各自的语义和选择依据

这是整篇文章的重头戏。last、break、redirect、permanent,四个flag分别代表四种完全不同的行为。

  • last:停止处理当前这个匹配块里的rewrite规则,但Nginx会根据改写后的新URI重新走一遍location匹配流程。
  • break:停止处理rewrite规则,且不再重新匹配location,就用当前location继续处理请求。
  • redirect:返回302临时重定向,URL变化可以在浏览器地址栏看到。
  • permanent:返回301永久重定向,告诉客户端"以后请直接用新地址访问,别再用旧地址了"。

对于last和break的区别,我见过太多的口头解释,但真正理解还是得靠场景。这里给一个经典例子:

location /download/ { rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; rewrite ^(/download/.*)/audio/(.*)\..*$ $1/music/$2.mp3 break; }

如果第一条last被匹配,Nginx会把URL改写为/download/xxx/mp3/xxx.mp3,然后重新匹配location。因为改写后的URL里包含了/mp3/,它可能会落到另一个专门处理mp3的location里。如果第二条break被匹配,Nginx不会重新选择location,而是继续在当前location里处理好这个请求。

3.4 什么时候用301,什么时候用302

很多初学者的困惑其实是重定向状态码的选择。我的建议很简单:

  • 网站结构性调整、页面永久迁移、域名变更,用permanent(301)。
  • 临时活动页、A/B测试、临时维护页面,用redirect(302)。
  • 如果你不确定未来会不会再改回来,先用302,等确认稳定了再换301。

特别注意:301一旦被浏览器和搜索引擎缓存,就很难收回。之前有个同事图省事,把临时维护通知配成了permanent,结果第二天发现维护结束要恢复,不少用户浏览器里还残留着指向维护页的跳转缓存,折腾了大半天才消停。

3.5 同样要会的return写法

rewrite可以发起重定向,但如果是简单的场景,直接return更省事:

server { return 301 https://newdomain.com$request_uri; }

这条配置在server-rewrite阶段就返回了301,不会走到后面的location匹配,也不涉及正则捕获,执行效率极高。你能明显感觉到它在压测下的开销比rewrite小。简短的、无复杂正则的重定向,能用return就别用rewrite。

4. if指令与逻辑判断:Nginx里最容易"出鬼"的配置块

4.1 if的条件语法能写什么

rewrite经常要和if搭配,用来做条件判断。Nginx的if语法其实非常原始,并不像编程语言那么全能,但支持的条件类型还是挺实用的:

if ($http_user_agent ~* "Spider|bot") { return 403; }

它支持以下几种条件:

  • 变量名直接判断:变量值为空串或"0"开头时被认为是false,否则为true。
  • 等值比较:=和!=,比较变量和字符串是否相等。
  • 正则匹配:~区分大小写匹配,~*不区分大小写,!~和!~*分别是对应的否定形式。
  • 文件与目录判断:-f判断文件是否存在,-d判断目录是否存在,-e判断文件、目录或软链接是否存在,-x判断文件是否可执行。

4.2 一个经典场景:判断请求的文件是否真实存在

伪静态场景里最常见的写法就是"如果请求的文件不存在,就重写到入口脚本"。ThinkPHP、Laravel这类PHP框架的老配置里经常看到:

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

这段的意思是:如果请求对应的本地文件(包括index.php这类脚本文件)不存在,就重写到/index.php?s=/$1,让框架来处理请求。$request_filename变量表示当前请求映射到磁盘的路径,!-e就是"不存在"。

这条规则本身没问题,但我在生产环境里见过太多因为这种写法衍生出的连锁事故。比如某次配置里,location块又加了别的rewrite规则,导致/index.php这个URL也被重写到入口脚本,形成了死循环。所以用if判断文件存在性时,一定要想清楚改写后的URL会落到哪个location,会不会再次匹配到同一条if规则。

4.3 Nginx if的"官方劝退"与我的使用原则

Nginx官方文档对if在location里的使用是持警告态度的,原话大意是"if在location里有很多难以预料的坑,尽量别用",特别点名了if里放proxy_pass这类指令可能产生预期外的行为。但你问我要不要彻底不用if,我的看法是:在server层用if做return/rewrite是安全的,在location层用if做简单判断也还能接受,但别在if里塞除rewrite/return/内部变量设置之外的复杂指令。

简单说,if在Nginx里就是个"轻量级守门员",让它干点判断和重写的活,问题不大;但别指望它像编程语言里的if那样万能。我自己的经验是:能用map指令实现的条件映射,就尽量用map;能用多个location精确匹配解决的,就尽量别用if。Nginx不是编程语言,配置文件也不是代码,能用声明式思维解决的场景,不要硬套过程式逻辑。

4.4 多条件判断的替代方案

Nginx的if不支持逻辑与(&&)、逻辑或(||),也不支持括号。如果你真的遇到了"UA是手机且访问路径是/old/"这种双条件场景,常见做法有几种。

一种是嵌套if。Nginx的if指令在serif语境下其实是支持嵌套的(在location内部嵌套可能触发坑,但在server层和部分场景下可用)。

另一种更推荐的方式是使用变量变量名拼接配合map模块。这里列一个map的写法:

map $http_user_agent $is_mobile { default 0; "~*iPhone|Android|Mobile" 1; } server { if ($is_mobile) { rewrite ^/news/(.*)$ /m/news/$1 last; } }

用map先把UA这个复杂条件"归一化"成一个布尔变量,再在if里只判断这个变量,逻辑清晰得多,也避免了复杂的正则写在主配置里。

5. 六个实战场景精讲:从域名到网关全覆盖

5.1 HTTPS强制跳转与主域名归一

这是我配置过最多、也最常见的rewrite场景。网站部署了HTTPS之后,要确保所有HTTP流量跳转到HTTPS;如果要做主域名统一,还要把www.example.com跳到example.com(或反过来)。

server { listen 80; server_name example.com www.example.com; return 301 https://example.com$request_uri; } server { listen 443 ssl; server_name example.com www.example.com; if ($host = 'www.example.com') { return 301 https://example.com$request_uri; } }

这里有几个细节值得说。$request_uri携带原始的完整路径和参数,重定向时用它保留用户访问的URL细节,体验最好。443端口的server里用if ($host = ...)做域名判断时,务必用=精确匹配而不是正则,避免误伤。这个场景用return而不是rewrite,因为不需要改写URI,只需要让浏览器跳转到新地址。

如果你用的是比较新的Nginx版本,443端口下的if ($host ...)写法可以换成增加一个独立的server块,用server_name区分,可读性更好。但if写法兼容性更通用,在一些老配置环境里改动更小。

5.2 伪静态规则设计:别让正则"吃"掉你的目录

伪静态是很多动态站点的刚需。拿一个典型的CMS规则来拆解:

location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php/php-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }

try_files按顺序检查$uri(请求的路径文件)、$uri/(请求的目录),都不存在的话再内部重写到/index.php?$args。这个方案的核心是:它只在文件确实不存在时才走PHP入口,避免了大量无意义的改写判断,性能比if+rewrite方案好很多。

但要注意location的匹配优先级。location ~ \.php$是正则location,它会优先于普通的location /前缀location。当try_files内部重写到/index.php后,请求会交给这个PHP location处理。这正是我们想要的效果。顺序看起来是先普通location再正则location,其实Nginx的匹配规则是:先找所有前缀匹配的location,记录最长的那个,然后再单独看有没有正则location能匹配上请求URI,有的话就用正则location。这个机制是理解伪静态配置的底层逻辑,务必背下来。

5.3 隐藏真实路径和入口参数

有时候不想让用户直接看到入口脚本名,比如把/api/v1/users对外暴露,实际上后端要的是/backend/index.php?route=v1/users。这个场景用rewrite在内部改写:

location /api/ { rewrite ^/api/(.*)$ /internal-api/$1 break; proxy_pass http://api_backend; }

注意这里的break,理由很关键:改写后的URI是/internal-api/xxx,如果不加break而用last,Nginx会重新匹配location。万一没有/internal-api/这个location,请求可能落到location /里走静态文件检查,直接被404。加了break后,Nginx停留在当前location,使用改写后的URI做proxy_pass转发,行为可控得多。

可能你会问,既然要用后端处理,为什么不直接proxy_pass带路径?原因是proxy_pass带URI和不带URI的行为在转发时会改变请求路径,很容易出现"路径被吞了一段"的问题。用rewrite先整理好URI,再交给proxy_pass无URI转发,是很多网关层配置的常规手法,也是我一直推荐的"先改写、再转发"模式。

5.4 基于UA重写到移动端页面

移动适配是老生常谈,虽然现在响应式布局普及后用得少了,但一些老业务还在用。原理很简单,通过判断$http_user_agent是否包含手机端特征,重写到专门目录:

server { listen 80; server_name example.com; if ($http_user_agent ~* "(iPhone|Android|Mobile)") { rewrite ^/(.*)$ /mobile/$1 last; } }

但如果/mobile/目录本身又要接受访问,这个写法会出问题——移动用户访问/mobile/index.html时,因为UA仍然符合条件,会被再次重写到/mobile/mobile/index.html,形成死循环。我见过有人在这上面栽跟头,调试了整整半天。

正确的做法是给移动端目录加一个排除判断,或者用location的嵌套匹配来隔离。稳妥的做法是这个:

server { listen 80; server_name example.com; location /mobile/ { # 移动端目录本身不需要再重写 } if ($http_user_agent ~* "(iPhone|Android|Mobile)") { rewrite ^/(.*)$ /mobile/$1 last; } }

呢这里last让改写后的/mobile/xxx重新匹配location,正好落到location /mobile/,不会再次进入if判断。看起来简单,但真需要把location匹配优先级和rewrite执行顺序吃透了才敢这么写。

5.5 rewrite配合proxy_pass做内部网关转发

在微服务架构里,Nginx常被当作API网关。请求的URL和内部服务的URL往往不一致,需要rewrite做映射。举个例子:

location /user-service/ { rewrite ^/user-service/(.*)$ /user/$1 break; proxy_pass http://user_service_upstream; }

这里有个重要细节:proxy_pass后如果不带URI(斜杠后面什么都没有),那么请求转发给后端时,使用的是rewrite改写后的URI。上面这个配置,/user-service/9527会被改写成/user/9527,后端服务收到的请求路径就是/user/9527。

但如果proxy_pass里带了URI,比如:

location /user-service/ { rewrite ^/user-service/(.*)$ /user/$1 break; proxy_pass http://user_service_upstream/prefix/; }

后端收到的路径就会是/prefix//user/9527这种奇怪的结果,双斜杠还可能触发后端框架的路由解析问题。所以在rewrite后接proxy_pass时,先想清楚要不要带URI,这是最容易出低级故障的配置点之一。

5.6 全站维护时的临时重定向

维护页的场景,通常用302临时跳转。有个小技巧值得分享:只把所有请求跳转到维护页,但放行静态资源(图片、CSS、JS)和维护页本身,不然连维护页样式都加载不出来。

location / { if ($request_uri !~ ^/(maintenance\.html|static/)) { return 302 /maintenance.html; } }

这段的意思是:只要请求的不是maintenance.html本身,也不是static/目录下的资源,就302跳到维护页。if配合!~做排除匹配,简单但有效。维护场景一定要用302而不是301,因为维护总会结束,301会被浏览器长时间缓存,一旦恢复线上环境,用户浏览器还残留着跳转。

6. 踩坑实录:调试Rewrite规则时最常见的五个问题

6.1 打开rewrite_log,让匹配过程"眼见为实"

我调试rewrite规则的第一件事,永远是打开rewrite_log。很多人的第一反应是靠直觉想,但rewrite的匹配链路涉及多个阶段,脑子里推演容易漏。

error_log /var/log/nginx/error.log notice; rewrite_log on;

配置后reload Nginx,访问一个测试URL,然后翻日志:

[notice] 1234#0: *1 "^/post/(\d+)\.html$" matches "/post/9527.html", client: 127.0.0.1 [notice] 1234#0: *1 rewritten data: "/article/9527", status: 301

日志会明确告诉你:哪条正则匹配了、改写成了什么、状态码是什么。配合curl -I观察返回的响应头和Location字段,几乎可以定位所有rewrite问题。这个习惯我保持了多年,强烈建议你也这么干。

6.2 死循环:最隐蔽的配置事故

死循环的本质是"改写后的URL再次命中了同一条rewrite规则"。最常见的一种写法是:

location / { rewrite ^/(.*)$ /index.php?s=$1 last; }

这条规则没有任何前置条件,也就是说/index.php这个URL本身也会被匹配,改写后变成/index.php?s=index.php,再次进入location匹配,再次被重写……Nginx默认有10次内部重定向的限制,超过会报错并返回500。日志里你会看到rewrite or internal redirection cycle。

避免死循环的核心原则:rewrite规则一定要"有限匹配",改写后的URL不能再命中改写规则本身。比如在规则前面加if (!-e $request_filename)或者用更精确的正则锚定,都能有效规避。

6.3 last和break用错后,请求直接404

这是一个非常典型的案例。某个前端项目配了如下规则:

location /app/ { rewrite ^/app/(.*)$ /static/$1 last; proxy_pass http://backend; }

问题在哪?last会让Nginx重新匹配location,改写后的/static/xxx如果没有对应的location,就会落到默认的location /,很可能变成静态文件404。而如果这里改成break,就会停留在当前location继续执行proxy_pass,把改写后的URI转发给后端。这个案例充分说明:rewrite和紧随其后的转发指令(proxy_pass、fastcgi_pass等)之间用什么flag衔接,必须看改写后的URL是否会落入其它location。

6.4 正则优先级和location嵌套顺序互相干扰

Nginx的location匹配规则是:先最长前缀匹配,再按顺序尝试正则匹配。这个顺序与很多人的直觉相反——不是"先写的先执行",而是"正则优先于普通前缀"。

location /images/ { rewrite ^/images/(.*)$ /cdn/images/$1 break; } location ~ \.(png|jpg|jpeg)$ { rewrite ^/(.*)$ /lossy/$1 last; }

这里/images/a.png会命中哪个rewrite?因为~ \.(png|jpg|jpeg)$是正则location,它会优先于/images/前缀location。所以你期待的图片CDN规则根本不会匹配到png,请求最终会进到/lossy/规则里。这个坑非常隐蔽,尤其是项目从Apache迁移到Nginx、把.htaccess规则机械翻译过来时,几乎人人都会踩一遍。

6.5 参数传递的意外丢失

rewrite匹配时默认丢了$args参数。比如:

rewrite ^/news/(\d+)$ /article.php?id=$1 last;

访问/news/9527?page=2,改写后的URI是/article.php?id=9527,但原来的page=2参数会被丢弃。如果你希望保留原有参数,可以这样写:

rewrite ^/news/(\d+)$ /article.php?id=$1&$args last;

或者如果不需要新增参数,直接用/article.php?id=$1&page=$arg_page这样显式透传。养成"主动考虑参数"的习惯,能帮你避免很多线上调试的深夜。

7. 性能意识与安全底线:Rewrite不能乱用

7.1 rewrite规则的匹配消耗比你想象的大

rewrite规则虽然方便,但每条正则都要在请求处理流程中执行匹配。如果server块里堆了几十条正则,每条请求进来都要遍历一遍,CPU消耗会明显上升,尤其是在高并发下。官方文档虽然没给硬性性能指标,但我在一次老项目压测中发现:同样1000并发,把30条无差别的rewrite规则优化成5条带锚点的精确规则后,P99延迟从130ms降到了85ms。

优化的思路有几个:

  • 能用return的不写rewrite,简单短路的指令开销最小。
  • 正则能加锚点就加锚点,减少不必要的回溯匹配。
  • 能将多个静态规则合并的,用一个正则搞定就不要拆成五条。
  • 能基于map做变量映射的,避免在if里反复做正则匹配。

7.2 last和break对性能路径的影响

很多人没意识到,last会触发一次新的location查找流程,这个内部重写是有额外开销的。break则停留在当前location,省去了重新匹配的成本。在高流量接口里,能break就不要last——当然,前提是逻辑上允许。

但这里也有个平衡问题:如果因为用了break导致请求落到错误的处理逻辑里,性能再优也是白扯。所以我的建议是:先在逻辑正确的前提下选flag,再在正确性有保证后再谈性能优化。

7.3 用rewrite做安全防护?先冷静

网上有很多"用rewrite防SQL注入""用rewrite封禁恶意UA"的教程,我的态度是:rewrite可以做一些基础层面的友好拦截,但它不该是你的安全防线主力。比如拒绝某个路径的访问:

location ~ ^/(\.env|\.git|composer\.json) { deny all; return 403; }

这类配置写起来方便,保护敏感文件确实有效。但真要防SQL注入、防CC攻击、防护WebShell,rewrite的正则匹配能力太原始,很容易被绕过。安全应该靠Web应用防火墙、后端参数校验、权限体系来解决,Nginx层做静态路径保护和限流就够了,别舍本逐末。

7.4 一条我坚持多年的配置自检清单

每次排完rewrite相关故障,我都会按这个清单检查一遍,这里直接分享出来:

  • 新的URL不会再匹配同一条rewrite规则,确认不会形成循环。
  • 需要保留参数时,显式添加了$args或$arg_xxx。
  • 重定向选对了状态码:永久迁移用301,临时维护用302。
  • rewrite后接proxy_pass时,确认代理目标要不要带URI。
  • Nginx -t 测试通过后再reload,避免线上变更直接生效导致事故。
  • 开启了rewrite_log并观察了一轮实际请求的日志输出。

这套清单帮我挡住了不止一次的误操作,也推荐你直接抄走。

我个人在实际操作中的体会是,rewrite模块是Nginx里少有的"看起来简单、深入后水很深"的配置点。你别指望一次性把语法记全就能写出完美的配置,真正的提升来源于每一个线上故障的复盘:为什么死循环了、为什么参数丢了、为什么请求走了错误的location。把这些经历沉淀下来,再回头看官方文档里的阶段表和flag定义,你会发现自己突然什么都通了。上面这些场景和坑,基本覆盖了我这些年遇到过的大部分rewrite问题,按图索骥去查,应该能帮你少走不少弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询