本文讨论如何创建 NGINX 重写规则;同样的方法适用于 NGINX Plus 和开源 NGINX。重写规则改变客户端请求 URL 的一部分或全部,通常出于以下两种目的之一:
- 告知客户端所请求的资源已经位于其他位置。例如,网站域名发生变化、希望客户端使用规范 URL 格式(带或不带 www 前缀),或希望捕获并纠正常见的域名拼写错误。
return和rewrite指令适用于这些用途。 - 控制 NGINX 内部的处理流程,例如当内容需要动态生成时,将请求转发到应用服务器。
try_files指令常用于这一目的。
提示:要了解如何将 Apache HTTP 服务器的重写规则转换为 NGINX 重写规则,请参阅配套文章将 Apache 重写规则转换为 NGINX 重写规则。
下文假设你已经熟悉 HTTP 响应码和正则表达式;NGINX 使用 Perl 语法。 (HTTP response codes;Perl)
比较 return、rewrite 和 try_files 指令
通用的 NGINX 重写指令有 return 和 rewrite;try_files 则是将请求导向应用服务器的一种便捷方式。下面回顾它们的作用与区别。
return 指令
在两种通用指令中,return 更简单,因此我们建议在可能的情况下用它代替 rewrite;后文会解释原因和适用时机。将 return 放在 server 或 location 上下文中,由上下文指定要重写的 URL,而指令定义客户端今后请求该资源时应使用的修正后 URL。 (return;server;location)
下面是将客户端重定向到新域名的简单示例:
server {
listen 80;
listen 443 ssl;
server_name www.old-name.com;
return 301 $scheme://www.new-name.com$request_uri;
}
listen 指令表示该 server 块同时适用于 HTTP 和 HTTPS 流量。server_name 匹配域名为 www.old-name.com 的请求 URL。return 告诉 NGINX 停止处理请求,并立即向客户端发送 301(永久移动)及指定的重写后 URL。新 URL 使用两个 NGINX 变量提取并复用原请求的值:$scheme 是协议(http 或 https),$request_uri 是包含参数的完整 URI。 (listen;server_name;NGINX variables)
对于 3xx 系列响应码,url 参数定义新的、重写后的 URL。
return (301 | 302 | 303 | 307) url;
对于其他响应码,可以选择定义出现在响应体中的字符串;HTTP 代码的标准文本,例如 404 的 Not Found,仍会包含在响应头中。该字符串可以包含 NGINX 变量。
return 1xx | 2xx | 4xx | 5xx) ["text"];
例如,在拒绝不含有效认证令牌的请求时,下面的指令可能适用:
return 401 "Access denied because token is expired or invalid";
还有一些语法上的简写,例如响应码为 302 时可以省略代码。请参阅 return 指令的参考文档。
有时,需要返回比一个字符串更复杂、更细致的响应。使用 error_page 指令,可以针对每个 HTTP 代码返回完整的自定义 HTML 页面,也可以改变响应码或执行重定向。
因此,return 易于使用,并适合同时满足两个条件的重定向:重写后的 URL 对匹配 server 或 location 块的每个请求都合适;并且可以使用标准 NGINX 变量构造该 URL。
rewrite 指令
如果需要检查 URL 之间更复杂的区别、捕获原 URL 中没有对应 NGINX 变量的元素,或修改、增加路径元素,该怎么办?这些情况下可以使用 rewrite。
与 return 一样,rewrite 位于定义待重写 URL 的 server 或 location 上下文中。除此之外,两者的区别多于相似点,正确使用 rewrite 可能更复杂。其语法很简单: (rewrite)
rewrite regex URL [flag];
但第一个参数 regex 意味着,除了匹配 server 或 location 指令,URL 还必须匹配指定正则表达式,NGINX 才会重写它。这个额外测试意味着更多处理工作。
第二个区别是,rewrite 只能返回 301 或 302。若要返回其他响应码,需要在 rewrite 之后加入 return,如下方示例所示。
最后,rewrite 不一定像 return 那样终止 NGINX 的请求处理,也不一定向客户端发送重定向。除非通过标志或 URL 语法明确要求停止处理或发送重定向,否则 NGINX 会遍历配置,寻找 Rewrite 模块定义的指令——break、if、return、rewrite 和 set——并依次处理。如果重写后的 URL 匹配后续 Rewrite 模块指令,NGINX 就对该 URL 执行指定操作,常常意味着再次重写。 (Rewrite)
此时情况可能复杂起来,需要仔细安排指令顺序,才能得到预期结果。例如,如果原来的 location 块及其中的重写规则又匹配重写后的 URL,NGINX 就可能进入循环,反复重写直到内置的 10 次上限。完整细节见 Rewrite 模块文档。因此,正如前面所建议的,应尽可能使用 return。
server {
# ...
rewrite ^(/download/.*)/media/(\w+)\.?.*$ $1/mp3/$2.mp3 last;
rewrite ^(/download/.*)/audio/(\w+)\.?.*$ $1/mp3/$2.ra last;
return 403;
# ...
}
前面提到,可以给 rewrite 添加标志来控制处理流程。示例中的 last 就是一种:它告诉 NGINX 跳过当前 server 或 location 块内后续 Rewrite 模块指令,开始寻找匹配重写后 URL 的新 location。
示例末尾的 return 表示,如果 URL 不匹配任何一条 rewrite,就向客户端返回 403。
try_files 指令
与 return 和 rewrite 一样,try_files 放在 server 或 location 块中。它接受一个或多个文件、目录,以及最后一个 URI 作为参数: (try_files)
try_files file ... uri;
NGINX 按顺序检查文件和目录是否存在,依据 root 与 alias 设置构造完整路径,并提供找到的第一个文件。表示目录时,在元素名末尾添加斜杠。如果所有文件或目录均不存在,就内部重定向到最后一个元素 uri 指定的 URI。 (root;alias)
要使 try_files 工作,还需要定义接收内部重定向的 location 块,如下例所示。最后一个元素也可以是命名 location,以 @ 开头表示。
try_files 常使用 $uri 变量,它表示 URL 中域名之后的部分。 ($uri)
下面的示例在客户端请求的文件不存在时提供默认 GIF。客户端请求例如 http://www.domain.com/images/image1.gif 时,NGINX 首先在适用于该 location 的 root 或 alias 指定的本地目录中查找 image1.gif;相关配置未在片段中展示。如果该文件不存在,就查找 image1.gif/;如果仍不存在,就重定向到 /images/default.gif。它与第二个 location 完全匹配,因此处理停止,NGINX 提供该文件,并将其标记为可缓存 30 秒。
location /images/ {
try_files $uri $uri/ /images/default.gif;
}
location = /images/default.gif {
expires 30s;
}
示例:规范化域名
NGINX 重写规则最常见的用途之一,是捕获过时或不规范的网站域名版本,并将其重定向到当前名称。下面有几种相关用法。
从旧域名重定向到当前域名
下面的规则将 www.old-name.com 和 old-name.com 的请求永久重定向到 www.new-name.com。它使用两个变量提取原请求 URL 的值:$scheme 为原协议(http 或 https),$request_uri 为域名之后的完整 URI,包括参数。
server {
listen 80;
listen 443 ssl;
server_name www.old-name.com old-name.com;
return 301 $scheme://www.new-name.com$request_uri;
}
由于 $request_uri 提取域名后的 URL 部分,这种重写适用于新旧站点页面一一对应的情况,例如 www.new-name.com/about 与 www.old-name.com/about 的基本内容相同。如果除了改域名还重新组织了站点,更安全的方法可能是省略 $request_uri,将所有请求重定向到首页:
server {
listen 80;
listen 443 ssl;
server_name www.old-name.com old-name.com;
return 301 $scheme://www.new-name.com;
}
有些介绍 NGINX URL 重写的博客使用 rewrite 完成这些用途,例如:
# NOT RECOMMENDED
rewrite ^ $scheme://www.new-name.com$request_uri permanent;
这种写法不如等价的 return 高效,因为 NGINX 需要处理正则表达式,即使它很简单——脱字符 ^,匹配整个原 URL。对应的 return 也更容易理解:与 rewrite ... permanent 相比,return 301 更清楚地表示 NGINX 返回 301。
添加与移除 www 前缀
下面两个示例分别添加和移除 www 前缀:
# add 'www'
server {
listen 80;
listen 443 ssl;
server_name domain.com;
return 301 $scheme://www.domain.com$request_uri;
}
# remove 'www'
server {
listen 80;
listen 443 ssl;
server_name www.domain.com;
return 301 $scheme://domain.com$request_uri;
}
这里同样优先选择 return,而不是下面的等价 rewrite。后者需要解释正则 ^(.*)$,并创建自定义变量 $1;它实际上等同于内置 $request_uri 变量。
# NOT RECOMMENDED
rewrite ^(.*)$ $scheme://www.domain.com$1 permanent;
将所有流量重定向到正确域名
下面是一个特殊情况:请求 URL 未匹配任何 server 和 location 块时,例如域名拼写错误,将进入的流量重定向到网站首页。它将 listen 的 default_server 参数与 server_name 的下划线参数结合使用。
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
return 301 $scheme://www.domain.com;
}
使用下划线作为 server_name 参数,是为了避免意外匹配真实域名;可以安全假定没有网站会使用下划线作为域名。不过,未匹配配置中其他 server 块的请求会来到这里;listen 的 default_server 告诉 NGINX 用这个块处理它们。重写 URL 时省略 $request_uri,就把所有请求重定向到首页。对于域名错误的请求,这通常是好主意,因为它们特别可能包含本站不存在的 URI。
示例:强制所有请求使用 SSL/TLS
这个 server 块强制所有访客使用安全的 SSL/TLS 连接访问站点。
server {
listen 80;
server_name www.domain.com;
return 301 https://www.domain.com$request_uri;
}
有些介绍 NGINX 重写规则的博客使用 if 检查加 rewrite 来完成这一用途,例如:
# NOT RECOMMENDED
if ($scheme != "https") {
rewrite ^ https://www.mydomain.com$uri permanent;
}
但该方法需要额外处理,因为 NGINX 既要计算 if 条件,又要处理 rewrite 中的正则表达式。
示例:为 WordPress 网站启用美观固定链接
对于使用 WordPress 的网站,NGINX 是很受欢迎的应用交付平台。下面的 try_files 告诉 NGINX 先检查文件 $uri,再检查目录 $uri/。如果两者都不存在,NGINX 内部重定向到 /index.php,并传递由 $args 捕获的查询字符串参数。
location / {
try_files $uri $uri/ /index.php?$args;
}
示例:丢弃不支持的文件扩展名请求
站点可能因为各种原因收到以某种文件扩展名结尾的请求,而你并没有运行处理它的应用服务器。Engine Yard 博客中的这个示例运行 Ruby on Rails,因此无法服务其他应用服务器处理的文件类型,例如 Active Server Pages、PHP、CGI 等,必须拒绝这些请求。在将动态生成资源请求交给应用的 server 块内,此 location 在请求到达 Rails 队列之前丢弃非 Rails 文件类型。
location ~ .(aspx|php|jsp|cgi)$ {
return 410;
}
严格来说,410(Gone,已永久消失)用于资源以前曾在此 URL 可用、如今不再可用,而且服务器不知道其当前位置的情况。与 404 相比,它的优点是明确表示资源永久不可用,因此客户端不会再次发送请求。
如果希望更准确地说明失败原因,可以返回 403(Forbidden,禁止),并以 "Server handles only Ruby requests" 之类的字符串解释。另一种方式是使用 deny all,返回 403,但不包含解释: (deny)
location ~ .(aspx|php|jsp|cgi)$ {
deny all;
}
403 隐含确认被请求资源存在。因此,如果希望通过尽可能少提供信息来达到“隐蔽式安全”,404 可能更合适。缺点是客户端可能反复重试,因为 404 不表示失败是暂时还是永久。
示例:配置自定义重新路由
在 MODXCloud 的这个示例中,一个资源充当一组 URL 的控制器。用户可以使用更易读的资源名;你将它重写到 listing.html 控制器处理,而不是对客户端重定向。
rewrite ^/listings/(.*)$ /listing.html?listing=$1 last;
例如,友好的 URL http://mysite.com/listings/123 被重写为由 listing.html 控制器处理的 http://mysite.com/listing.html?listing=123。
相关文档
将 NGINX 和 NGINX Plus 配置为 Web 服务器

原文:创建 NGINX 重写规则;作者:Tony Mauro;发表于 2015-10-07。
版权归原作者及来源机构所有。版本、性能数字与活动时间保留原文语境;示例未在本环境执行。











暂无评论内容