Nginx Rewrite 基础教程 – wiki基地


Nginx Rewrite 基础教程:URL 重写的艺术与实践

引言

在现代 Web 开发和服务器管理中,对访问的 URL 进行处理、转换或重定向是司空见惯的需求。无论是为了 SEO 优化、用户体验提升、系统架构调整,还是简单的路径美化,URL 重写都扮演着核心角色。Nginx 作为一款高性能的 Web 服务器和反向代理,其强大的 URL 重写功能是其核心竞争力之一。

Nginx 的 Rewrite 模块允许管理员通过配置规则,根据请求的 URI、Host、User-Agent 等信息,动态地修改请求的 URL,然后 Nginx 会根据新的 URL 继续处理请求(内部重写)或通知客户端进行跳转(外部重定向)。掌握 Nginx Rewrite 是每一个 Nginx 使用者必备的技能。

然而,由于其强大的功能和基于正则表达式的匹配方式,Rewrite 配置有时也显得复杂且容易出错,甚至可能导致意外的循环重定向。因此,深入理解其工作原理和语法规则至关重要。

本文将详细介绍 Nginx Rewrite 的基础知识,包括正则表达式、rewrite 指令的语法、各种标志(flags)的作用、Rewrite 的处理流程以及如何在不同的上下文中应用和调试 Rewrite 规则,并通过丰富的实例帮助读者掌握这一强大的工具。

一、理解 URL 重写的概念

简单来说,URL 重写就是服务器接收到一个 URL 请求后,在内部或外部将其转换为另一个 URL 的过程。

  • 内部重写 (Internal Rewrite): 服务器接收到请求 /old/path,通过配置规则将其转换为 /new/path,然后服务器内部直接去查找和处理 /new/path 对应的资源。客户端对此过程是无感的,浏览器地址栏显示的仍是 /old/path
  • 外部重定向 (External Redirect): 服务器接收到请求 /old/page.html,通过配置规则判断需要重定向到 /new/page.html。服务器会向客户端发送一个特殊的 HTTP 响应(通常是 301 或 302 状态码),告诉浏览器新的地址是 /new/page.html。浏览器收到响应后,会向新地址发起新的请求。此时,浏览器地址栏会显示 /new/page.html

Nginx 的 rewrite 指令可以实现这两种类型的重写。

二、Nginx Rewrite 的基石:正则表达式

Nginx Rewrite 规则的核心在于使用正则表达式(Regular Expressions)来匹配请求的 URI。掌握常用的正则表达式语法是编写有效 Rewrite 规则的前提。

以下是一些 Nginx Rewrite 中常用的正则表达式元字符和语法:

  • .: 匹配除换行符以外的任意单个字符。
  • *: 匹配前一个字符零次或多次。
  • +: 匹配前一个字符一次或多次。
  • ?: 匹配前一个字符零次或一次(使其变为可选)。也可以用于使 *+ 的匹配变为“非贪婪”模式,尽可能少地匹配。
  • ^: 匹配行的开头。在 Nginx 中通常匹配 URI 的开头。
  • $: 匹配行的结尾。在 Nginx 中通常匹配 URI 的结尾。
  • |: 或操作符,匹配 | 前或后的表达式。例如:GET|POST 匹配 “GET” 或 “POST”。
  • (): 捕获组。将括号内的匹配内容捕获到一个变量中,可以在替换字符串中使用 $1, $2, …, $9 来引用这些捕获的内容。$1 引用第一个捕获组的内容,$2 引用第二个,以此类推。
  • []: 字符集。匹配方括号内的任意一个字符。例如:[abc] 匹配 ‘a’, ‘b’, 或 ‘c’。[0-9] 匹配任意数字,[a-zA-Z] 匹配任意字母。
  • [^...]: 否定字符集。匹配不在方括号内的任意一个字符。例如:[^/] 匹配除斜杠以外的任意字符。
  • \d: 匹配任意数字,等同于 [0-9]
  • \w: 匹配任意字母、数字或下划线,等同于 [a-zA-Z0-9_]
  • \s: 匹配任意空白字符。
  • \: 转义字符。用于匹配具有特殊含义的元字符本身,例如 \. 匹配点号,\/ 匹配斜杠。

在 Nginx Rewrite 中,正则表达式通常用于匹配请求的 URI(不包含域名和查询字符串)。 例如,对于请求 http://example.com/article/123?foo=bar,Rewrite 规则通常匹配的部分是 /article/123

示例:

  • 匹配以 /old-path 开头的 URI: ^/old-path
  • 匹配精确的 /about.html: ^/about\.html$ (注意点号需要转义)
  • 匹配 /article/ 后跟一个或多个数字的 URI,并捕获这些数字: ^/article/(\d+)$
  • 匹配 /download/ 后跟任意文件名和扩展名,并捕获文件名(不含扩展名):^/download/(.*)\.zip$

三、rewrite 指令的语法

rewrite 指令是 Nginx Rewrite 模块的核心。其基本语法如下:

nginx
rewrite regex replacement [flag];

让我们详细解析这三个部分:

  1. regex (正则表达式): 用于匹配请求 URI 的正则表达式。如果 URI 匹配了这个正则表达式,Nginx 就会执行相应的重写规则。
  2. replacement (替换字符串):regex 匹配成功时,用于替换原始 URI 的新字符串。这个字符串可以包含:

    • 静态文本:例如 /new/path/
    • 捕获组引用:使用 $1, $2, … 来引用 regex 中捕获组 () 匹配的内容。
    • Nginx 内置变量:例如 $uri, $request_uri, $host, $server_port, $args 等。
      • $uri: 当前请求的 URI,不包含域名和查询字符串。
      • $request_uri: 完整的原始请求 URI,包含查询字符串(例如 /path?arg=value)。
      • $host: 请求头中的 Host 字段,如果不存在则为服务器名。
      • $args: 请求中的查询字符串。
      • $is_args: 如果请求中带有参数,值为 ?,否则为空。常用于拼接查询字符串。
    • 组合:静态文本、捕获组引用和 Nginx 变量可以自由组合。

    注意: 如果 replacementhttp://, https://, 或 $scheme 开头,则表示这是一个外部重定向。否则,这是一个内部重写

  3. flag (标志): 可选参数,用于控制 Rewrite 规则的行为。这是理解 Nginx Rewrite 复杂性的关键部分。常用的标志有四个:

    • last:

      • 行为: 完成当前的 Rewrite 规则后,停止处理当前 location 块中的剩余 Rewrite 规则,然后使用新的 URI 重新查找(internal redirect)匹配的 location 块。
      • 用途: 常用于在 server 块中或 location 块的末尾进行内部重写,将请求导向到一个新的 location 块进行处理。例如,将伪静态 URL 重写为内部脚本路径。
      • 注意: 如果 last 用在 server 块中,它会导致 Nginx 使用新的 URI 再次尝试匹配 server 块中的 location 块。如果用在 location 块中,它也会导致 Nginx 使用新的 URI 再次尝试匹配 server 块中的 location 块(从头开始)。这正是它叫做 last(最后的)的原因——它结束了当前这一轮的 URI 处理和 location 匹配,并以新的 URI 启动新的一轮。
    • break:

      • 行为: 完成当前的 Rewrite 规则后,停止处理当前 location中的剩余 Rewrite 规则。不会使用新的 URI 重新查找 location 块。Nginx 将根据新的 URI 在当前 location 块内继续进行其他指令的处理(例如 root, index, try_files)。
      • 用途: 常用于在特定的 location 块中停止后续 Rewrite 处理,直接处理当前请求。例如,在 /static/ 这样的 location 中,你可能想做一些简单的重写(如去掉版本号),然后直接提供文件,而不希望它跳到其他 location
      • 注意: break 只停止当前 location 块内的 Rewrite 处理,不会影响 server 块中的 Rewrite 或其他 location 块。它也不会导致 Nginx 重新匹配 location
    • redirect:

      • 行为: 执行一个外部重定向,使用 302 Found 状态码。新的 URL 会在响应头的 Location 字段中返回给客户端。
      • 用途: 用于临时性的 URL 重定向。客户端浏览器地址栏会显示新的 URL。
      • 注意: replacement 必须以 http://, https://, 或 $scheme 开头,或者是一个完整的路径(不建议只写路径,可能导致不完整 URL 的重定向)。通常与完整的 URL 一起使用。
    • permanent:

      • 行为: 执行一个外部重定向,使用 301 Moved Permanently 状态码。新的 URL 会在响应头的 Location 字段中返回给客户端。
      • 用途: 用于永久性的 URL 重定向。搜索引擎会根据 301 状态码更新其索引,并将旧 URL 的权重转移到新 URL。客户端浏览器地址栏会显示新的 URL。
      • 注意:redirect 类似,replacement 通常需要是一个完整的 URL 或以 http://, https://, $scheme 开头。

last vs break 总结:

标志 是否停止当前块的 Rewrite 是否重新查找 location 在当前块内继续处理 主要用途
last 将请求转发到另一个 location
break 在当前 location 内完成处理

理解这两个标志的区别是避免 Rewrite 配置错误的关键。last 是“跳出去,再匹配一次”,而 break 是“停下来,就在这处理”。

redirect vs permanent 总结:

标志 状态码 搜索引擎处理方式 浏览器缓存 用途
redirect 302 不会更新索引 不缓存 临时重定向
permanent 301 更新索引,传递权重 可能永久缓存 永久重定向

四、rewrite 指令的上下文(Contexts)

rewrite 指令可以在 server, location, 和 if 块中使用。它们在不同上下文中的行为略有差异:

  1. server 块:

    • server 块中的 rewrite 指令会在 Nginx 找到匹配的 server 后、查找 location 块之前执行。
    • server 块中的 Rewrite 会按顺序逐条执行。
    • 如果某条 rewrite 规则使用了 last 标志,Nginx 会根据新的 URI 重新查找 location 块(从 server 块的开头重新匹配 location)。
    • 如果使用了 break, redirect, 或 permanent 标志,或者规则没有匹配,Nginx 会继续处理后续指令(包括查找 location)。
  2. location 块:

    • location 块中的 rewrite 指令会在 Nginx 找到匹配的 location 后执行。
    • location 块中的 Rewrite 也会按顺序逐条执行。
    • 如果某条 rewrite 规则使用了 last 标志,Nginx 会根据新的 URI 重新查找 location 块(从 server 块的开头重新匹配 location)。这与在 server 块中使用 last 的行为是一致的。
    • 如果某条 rewrite 规则使用了 break 标志,Nginx 会停止处理当前 location 块中剩余的 Rewrite 规则,然后继续处理当前 location 块中的其他指令 (root, index, try_files 等)。不会重新查找 location
    • 如果使用了 redirectpermanent 标志,Nginx 会执行外部重定向并停止处理。
    • 如果规则没有匹配,Nginx 会继续处理当前 location 块中的下一条 Rewrite 规则,或者处理当前 location 块中的其他指令。
  3. if 块:

    • rewrite 指令也可以放在 if 块内部。if 指令允许基于更复杂的条件(如变量值)来执行 Rewrite 或其他指令。
    • if 块中的 rewrite 指令的行为与在 location 块中类似,但只在 if 条件为真时执行。

Rewrite 处理顺序总结:

Nginx 处理一个请求时,Rewrite 规则的执行顺序大致如下:

  1. 处理 server 块中的 rewrite 指令(按顺序)。
  2. 如果 server 块中的 Rewrite 规则使用了 last 标志,则使用新的 URI 再次从第 1 步开始处理(但通常只会在查找 location 前发生)。
  3. 查找匹配的 location 块。
  4. 处理匹配到的 location 块中的 rewrite 指令(按顺序)。这包括 location 内的 if 块中的 Rewrite。
  5. 如果 location 块中的 Rewrite 规则使用了 last 标志,则使用新的 URI 再次从第 3 步开始处理(重新查找 location)。
  6. 如果 location 块中的 Rewrite 规则使用了 break 标志,则停止当前块的 Rewrite 处理,在当前 location 内继续执行其他指令。
  7. 如果使用了 redirectpermanent,则执行外部重定向并终止。
  8. 如果没有 Rewrite 发生或 Rewrite 处理完毕(没有 lastbreak),Nginx 继续处理当前 location 块内的其他指令(如 root, index, try_files, proxy_pass 等)。

重要提示: 避免在一个 location 块中混合使用 rewriteproxy_pass 指令。虽然在某些情况下可以工作,但这通常不是最佳实践,且可能导致难以预料的行为。通常的最佳做法是使用 rewrite ... last; 将请求重写并转发到另一个 location 块,然后在那个块中使用 proxy_pass。或者,对于简单的转发,直接在 location 块中使用 proxy_pass 配合匹配规则。

五、实践示例

以下是一些常见的 Nginx Rewrite 使用场景及其配置示例:

示例 1:简单的路径重写 (内部重写)

/old-path/ 重写为 /new-path/

“`nginx
location /old-path/ {
rewrite ^/old-path/(.)$ /new-path/$1 last;
# 或者
# rewrite ^/old-path/(.
)$ /new-path/$1 break; # 如果 /new-path/ 也是当前 location 处理
}

location /new-path/ {
# 处理 /new-path/ 的配置,例如
# root /var/www/html/new;
# index index.html;
}
``
**解释:**
*
^/old-path/(.*)$匹配以/old-path/开头,后面跟任意内容,并将后面内容捕获到$1
*
/new-path/$1是替换字符串,将/old-path/替换为/new-path/,并带上捕获的子路径。
*
last标志使得 Nginx 使用/new-path/…重新查找location,最终匹配到location /new-path/` 进行处理。

示例 2:永久重定向旧页面到新页面 (外部重定向)

/old-page.html 永久重定向到 /new-page.html

“`nginx
server {
listen 80;
server_name example.com;

rewrite ^/old-page\.html$ /new-page.html permanent; # 在 server 块或 location 块都可以

location / {
    # 其他配置
}

location /new-page.html {
    # 处理新页面的配置
}

}
``
**解释:**
*
^/old-page.html$精确匹配/old-page.html
*
/new-page.html是替换字符串。由于它不是以http://等开头,Nginx 默认会构建一个完整的 URL 进行重定向(例如http://example.com/new-page.html)。
*
permanent` 标志发送 301 状态码进行永久重定向。

更严谨的永久重定向写法(推荐):

nginx
rewrite ^/old-page\.html$ $scheme://$host/new-page.html permanent;

解释: 使用 $scheme$host 确保重定向后的 URL 包含正确的协议和域名,避免潜在问题。

示例 3:规范化 URL – 添加或移除 www

example.com 的访问重定向到 www.example.com

“`nginx
server {
listen 80;
listen 443 ssl;
server_name example.com; # 匹配 non-www

# 永久重定向到 www
rewrite ^(.*)$ $scheme://www.example.com$1 permanent;

# 或者
# return 301 $scheme://www.example.com$request_uri; # 更简单的方式,无需 regex

# 其他配置...

}

server {
listen 80;
listen 443 ssl;
server_name www.example.com; # 处理 www 的请求

# 其他配置...

}
``
**解释:**
* 第一个
server块监听example.com
*
rewrite ^(.*)$匹配所有请求 URI,并将其捕获到$1
*
$scheme://www.example.com$1构建新的 URL,将域名改为www.example.com
*
permanent` 标志发送 301 状态码。

示例 4:强制 HTTPS

将所有 HTTP 请求重定向到 HTTPS。

“`nginx
server {
listen 80;
server_name example.com www.example.com;

# 将所有 HTTP 请求重定向到 HTTPS
rewrite ^(.*)$ https://$host$1 permanent;

# 或者
# return 301 https://$host$request_uri; # 更简单的方式,推荐用于简单重定向

# 其他配置...

}

server {
listen 443 ssl;
server_name example.com www.example.com;

# HTTPS 配置...

}
``
**解释:**
* 第一个
server块监听 80 端口(HTTP)。
*
rewrite ^(.*)$匹配所有请求 URI。
*
https://$host$1构建新的 URL,将协议改为https,域名保持不变,带上原始 URI。
*
permanent` 标志发送 301 状态码。

示例 5:美化动态 URL (伪静态)

/article/123 这样的 URL 内部重写为 /news.php?id=123

“`nginx
location /article/ {
rewrite ^/article/(\d+)$ /news.php?id=$1 last;
}

location ~ .php$ {
# PHP 相关的处理配置,例如 fastcgi_pass
# include fastcgi_params;
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# fastcgi_pass unix:/var/run/php-fpm.sock; # 或者 fastcgi_pass 127.0.0.1:9000;
}
``
**解释:**
*
location /article/匹配以/article/开头的请求。
*
^/article/(\d+)$匹配/article/后跟一个或多个数字,并将数字捕获到$1
*
/news.php?id=$1是替换字符串,构建新的内部 URI,将捕获的数字作为id参数。
*
last标志使得 Nginx 使用/news.php?id=…重新查找location,从而匹配到处理.php文件的location` 块。

示例 6:移除 URL 中的 /index.html

/index.html 重定向到 /

“`nginx
server {
listen 80;
server_name example.com;

# 在 server 块或 location / 块都可以
rewrite ^/index\.html$ / permanent;

location / {
    # 其他配置,例如
    root /var/www/html;
    index index.html index.htm; # Nginx 默认也会处理 / 请求并查找 index 文件
}

}
``
**解释:**
*
^/index.html$精确匹配/index.html
*
/是替换字符串。
*
permanent` 标志发送 301 状态码。

示例 7:使用 break 停止 Rewrite 处理

假设你有一个 /static/ 目录,你只想对其内部进行简单的重写,然后直接提供文件。

“`nginx
location /static/ {
# 假设你想把 /static/v1.0/css/style.css 改写成 /static/css/style.css 提供
rewrite ^/static/v\d+.\d+/(.*)$ /static/$1 break;

root /var/www/static;
# try_files $uri $uri/ =404;

}
``
**解释:**
*
location /static/匹配静态文件请求。
*
rewrite ^/static/v\d+.\d+/(.*)$ /static/$1 break;匹配带有版本号路径的请求,并移除版本号,捕获子路径到$1
*
/static/$1是替换字符串。
*
break标志停止了当前location块中的 Rewrite 处理。Nginx 将使用新的 URI(例如/static/css/style.css)在当前的location /static/块中继续处理,结合root /var/www/static;指令,最终尝试提供/var/www/static/static/css/style.css` 文件。

六、Rewrite 的替代方案与注意事项

虽然 rewrite 指令功能强大,但有时它并不是唯一的选择,甚至不是最佳选择。

  • return 指令: 对于简单的外部重定向,return 指令通常更简洁高效。它可以直接返回指定的 HTTP 状态码和 URL,无需使用正则表达式。
    “`nginx
    # 重定向 /old 到 /new (301)
    location = /old {
    return 301 /new;
    }

    重定向 /old-path/ 到 /new-path/ (302)

    location /old-path/ {
    return 302 /new-path/$request_uri; # $request_uri 包含完整路径和参数
    }
    ``return指令的执行效率通常比rewrite` 更高,且更易读,对于纯粹的重定向场景优先考虑使用它。

  • try_files 指令: try_files 指令主要用于检查文件或目录是否存在,并根据结果执行内部重写或返回错误。它在处理静态文件和伪静态场景中非常有用,可以部分替代 rewrite ... last; 的功能。
    “`nginx
    location / {
    # 尝试查找 $uri 对应的文件,如果不存在,尝试查找 $uri/ 对应的目录下的 index 文件
    # 如果都不存在,内部重写到 /index.php 并带上查询参数
    try_files $uri $uri/ /index.php?$args;
    }

    location ~ .php$ {
    # 处理 PHP 请求
    # …
    }
    ``
    这个例子中,
    /some/path会尝试查找文件/some/path,然后尝试查找目录/some/path/下的索引文件。如果都找不到,则内部重写到/index.php?args。这种方式比使用rewrite` 规则来判断文件是否存在更直观。

  • if 指令: 虽然 rewrite 可以在 if 块中使用,但 Nginx 的官方文档建议谨慎使用 if 指令,尤其是在 location 块中,因为它的行为有时可能出乎意料,尤其与其他指令(如 rewrite, try_files, proxy_pass)结合使用时。对于复杂的条件逻辑,考虑将配置拆分到不同的 server 块或 location 块,或者使用 map 指令来设置变量。

注意事项:

  1. 正则表达式的准确性: 错误的正则表达式是导致 Rewrite 失败或意外匹配的最常见原因。仔细测试你的正则表达式。
  2. 标志的选择: 正确选择 last, break, redirect, permanent 标志至关重要,它们直接影响 Rewrite 的行为和后续请求处理流程。
  3. Rewrite 循环: 不正确的 Rewrite 规则可能导致无限循环重定向,耗尽服务器资源并导致客户端浏览器报错。仔细检查规则,确保不会将 URI 重写回一个会再次被同一或另一条规则匹配的 URI。使用 permanent 时尤其小心,因为它会被浏览器缓存。
  4. 处理顺序: 理解 Nginx 处理 Rewrite 规则以及与其他指令(如 try_files, location 匹配)的交互顺序非常重要。
  5. 调试: 当 Rewrite 不工作时,可以增加 Nginx 的错误日志级别(例如到 infodebug,但 debug 级别日志量巨大,通常只在测试环境使用),并在 error_log 文件中查找与 rewrite 相关的输出,这通常能提供关于规则匹配和处理过程的有价值信息。

七、总结

Nginx Rewrite 是一个功能强大的 URL 处理工具,它基于正则表达式,通过 rewrite 指令及其标志,可以实现灵活的内部重写和外部重定向。掌握 regex, replacement, 和 flag 的语法及其在不同上下文(server, location, if)中的行为,是高效配置 Nginx 的关键。

尽管 Rewrite 功能强大,但在简单的重定向场景中,优先考虑使用更直接的 return 指令。对于文件查找和伪静态处理,try_files 往往是更清晰的选择。合理结合使用这些指令,并理解它们之间的处理顺序,可以帮助你构建健壮、高效且易于维护的 Nginx 配置。

通过本文的介绍和示例,希望你能对 Nginx Rewrite 有一个全面而深入的理解。实践是最好的老师,尝试在你的 Nginx 环境中配置和测试不同的 Rewrite 规则,逐步掌握这项重要的技能。记住,小心、耐心、反复测试是避免 Rewrite 陷阱的最佳途径。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部