lolly/docs/nginx/06-nginx-rewrite.md
xfy 972eab4267 refactor(docs): 重构文档目录结构,nginx 文档移至子目录
将 docs/ 根目录下的 nginx 相关文档统一移动到 docs/nginx/ 子目录,
提高文档组织性和可维护性。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 10:48:14 +08:00

8.9 KiB
Raw Permalink Blame History

NGINX URL 重写与请求处理指南

1. rewrite 模块概述

ngx_http_rewrite_module 模块用于:

  • 使用 PCRE 正则表达式更改请求 URI
  • 返回重定向
  • 有条件地选择配置

处理顺序

  1. 按顺序执行 server 级别的 rewrite 指令
  2. 根据请求 URI 搜索 location
  3. 按顺序执行 location 内的 rewrite 指令
  4. 如果 URI 被 rewrite 更改,重复循环(不超过 10 次)

2. rewrite 指令

语法

rewrite regex replacement [flag];

# 基础重写
rewrite ^/old/(.*)$ /new/$1 permanent;

# 重定向
rewrite ^/download/(.*)$ /files/$1 redirect;

# 内部重写
rewrite ^/images/(.*)\.jpg$ /pics/$1.png last;

Flags 参数

Flag 说明
last 停止当前指令集处理,开始搜索新 location
break 停止当前指令集处理,继续在当前 location 中处理
redirect 返回 302 临时重定向
permanent 返回 301 永久重定向

示例

server {
    # Server 级别 - 使用 last
    rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last;
    rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra  last;

    location /download/ {
        # Location 级别 - 使用 break
        rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break;
        rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra  break;
    }
}

参数处理

# replacement 包含新参数时,旧参数自动追加
rewrite ^/users/(.*)$ /show?user=$1? last;
# ? 结尾表示不追加旧参数

3. return 指令

语法

return code [text];
return code URL;
return URL;

状态码使用

状态码 类型 用途
301 永久重定向 URL 永久变更
302 临时重定向 URL 临时变更
303 临时重定向 POST 后重定向到 GET
307 临时重定向 保持请求方法
308 永久重定向 保持请求方法
444 非标准 关闭连接(不发送响应)

示例

# 返回文本
location /status {
    return 200 "OK";
}

# 返回 JSON
location /json {
    default_type application/json;
    return 200 '{"status": "ok"}';
}

# 重定向
location /old {
    return 301 /new;
}

# 域名重定向
server {
    listen 80;
    server_name old.example.com;
    return 301 http://new.example.com$request_uri;
}

# HTTP 到 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

# 拒绝请求
location /private {
    return 403;
}

# 关闭连接(不发送响应)
server {
    listen 80 default_server;
    server_name _;
    return 444;
}

4. if 指令

语法

if (condition) { ... }

条件类型

类型 示例 说明
变量判断 if ($variable) 空字符串或 "0" 为假
字符串相等 if ($a = "value") 等于
字符串不等 if ($a != "value") 不等于
正则匹配 if ($a ~ pattern) 区分大小写
正则匹配 if ($a ~* pattern) 不区分大小写
正则不匹配 if ($a !~ pattern) 不匹配
文件存在 if (-f $uri) 文件存在
文件不存在 if (!-f $uri) 文件不存在
目录存在 if (-d $uri) 目录存在
目录不存在 if (!-d $uri) 目录不存在
文件/目录存在 if (-e $uri) 存在
可执行 if (-x $uri) 可执行文件

示例

# 浏览器判断
if ($http_user_agent ~ MSIE) {
    rewrite ^(.*)$ /msie/$1 break;
}

# 请求方法判断
if ($request_method = POST) {
    return 405;
}

# 文件不存在时
if (!-f $request_filename) {
    rewrite ^(.*)$ /index.php last;
}

# 防盗链
if ($invalid_referer) {
    return 403;
}

# 限制速度
if ($slow) {
    limit_rate 10k;
    break;
}

注意事项

  • if 块会创建单独的配置上下文
  • 避免在 if 中使用某些指令(可能导致意外行为)
  • 推荐使用 try_filesmap 替代复杂条件判断

5. break 指令

停止处理当前 rewrite 模块指令集。

location /download/ {
    if ($forbidden) {
        return 403;
    }

    if ($slow) {
        limit_rate 10k;
        break;  # 停止 rewrite 模块处理,继续 location 处理
    }

    rewrite ^(.*)$ /files/$1 break;
}

6. set 指令

设置变量值。

set $variable value;

# 示例
set $mobile false;

if ($http_user_agent ~ "Mobile") {
    set $mobile true;
}

location /api {
    if ($mobile = true) {
        proxy_pass http://mobile_backend;
    }
    proxy_pass http://desktop_backend;
}

7. map 指令(替代复杂 if

语法

map source $variable { ... }

示例

http {
    # 浏览器映射
    map $http_user_agent $mobile {
        default         false;
        "~*Mobile"      true;
        "~*Android"     true;
        "~*iPhone"      true;
    }

    # 环境映射
    map $host $backend {
        default         http://backend.prod;
        dev.example.com http://backend.dev;
        test.example.com http://backend.test;
    }

    # 日志级别映射
    map $status $loggable {
        ~^[23]  0;  # 2xx/3xx 不记录
        default 1;
    }

    server {
        location / {
            if ($mobile = true) {
                proxy_pass http://mobile_backend;
            }
            proxy_pass $backend;
        }

        access_log /var/log/nginx/access.log combined if=$loggable;
    }
}

8. 常见重写场景

域名重定向

# 旧域名到新域名
server {
    listen 80;
    server_name old.example.com;
    return 301 http://new.example.com$request_uri;
}

# 多域名统一
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# www 到非 www
server {
    listen 80;
    server_name www.example.com;
    return 301 http://example.com$request_uri;
}

# 非 www 到 www
server {
    listen 80;
    server_name example.com;
    return 301 http://www.example.com$request_uri;
}

HTTP 到 HTTPS

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    # ...
}

URL 结构变更

# 产品页面重写
rewrite ^/products/([0-9]+)$ /product?id=$1 last;

# 分类页面
rewrite ^/category/([a-z]+)$ /cat?name=$1 last;

# API 版本迁移
rewrite ^/api/v1/(.*)$ /api/v2/$1 permanent;

静态文件处理

location / {
    try_files $uri $uri/ @fallback;
}

location @fallback {
    rewrite ^(.*)$ /index.php last;
}

# 图片路径重写
rewrite ^/images/(.*)\.(gif|jpg|png)$ /static/$1.$2 last;

PHP 应用重写

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
    fastcgi_pass localhost:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

WordPress 重写

location / {
    try_files $uri $uri/ /index.php?$args;
}

# WordPress 固定链接支持
rewrite /wp-admin$ /wp-admin/ permanent;

9. 防盗链配置

location ~* \.(gif|jpg|png|swf|flv)$ {
    valid_referers none blocked server_names *.example.com example.*;

    if ($invalid_referer) {
        return 403;
        # 或返回替代图片
        # return 301 http://example.com/nolink.png;
    }
}

10. rewrite_log 指令

启用重写日志记录。

rewrite_log on;   # 将 rewrite 处理结果记录到 error_lognotice 级别)

11. 内部实现说明

rewrite 指令在配置阶段编译为内部指令,请求处理时由虚拟栈机器解释执行。

配置示例编译结果

location /download/ {
    if ($forbidden) {
        return 403;
    }
    rewrite ^/(.*)$ /files/$1 break;
}

编译为内部指令:

variable $forbidden
check against zero
    return 403
    end of code
match of regular expression
copy $1
copy "/files/"
end of regular expression
end of code

12. 最佳实践

优先使用 return 而非 rewrite

# 推荐
return 301 /new;

# 不推荐
rewrite ^(.*)$ /new permanent;

使用 try_files 替代复杂的 if 文件检查

# 推荐
location / {
    try_files $uri $uri/ @fallback;
}

# 不推荐
location / {
    if (!-e $request_filename) {
        rewrite ^(.*)$ /index.php last;
    }
}

使用 map 替代重复的 if 判断

# 推荐
http {
    map $http_user_agent $is_mobile {
        default      false;
        "~*Mobile"   true;
    }
}

# 不推荐
location / {
    if ($http_user_agent ~ Mobile) {
        set $is_mobile true;
    }
}

Location 级别使用 break

location /download/ {
    rewrite ^(.*)$ /files/$1 break;
    # 使用 break 而非 last避免 10 次循环错误
}

注意循环限制

rewrite 循环不超过 10 次,超过会返回 500 错误。

# 错误示例(无限循环)
rewrite ^/test$ /test last;