nginx rewrite指令

Nginx rewrite 指令详解

你已经把基本概念总结得很清楚了,我来补充一些更深入的理解、实际应用场景以及注意事项,帮你更全面地掌握 rewrite 指令。


一、rewrite 指令完整语法

rewrite 正则表达式 替换内容 [flag];

执行流程

  1. 检查请求 URI 是否匹配正则表达式
  2. 如果匹配,将 URI 替换为新的内容
  3. 根据 flag 决定后续行为

二、flag 标记的深入理解

1. last 和 break 的区别(重点)

维度lastbreak
是否重新匹配 location✅ 会❌ 不会
执行位置完成 rewrite 后,重新搜索 location停止 rewrite,继续执行当前 location
适用场景rewrite 后需要进入新的 locationrewrite 后还在当前 location 处理
性能稍低(需重新匹配)更高

实例对比

server {
    listen 80;
    server_name example.com;

    location /test/ {
        rewrite ^/test/1.html /test/2.html last;
        rewrite ^/test/2.html /test/3.html break;
        rewrite ^/test/3.html /test/4.html last;

        add_header X-Rewrite-Status "executed";
        return 200 "最终 URI: $uri";
    }

    location /test/2.html {
        return 200 "这是 location /test/2.html";
    }
}

请求 /test/1.html 的执行流程

1. 匹配 ^/test/1.html → 重写为 /test/2.html
2. last 标志 → 重新搜索 location
3. 进入 location /test/2.html
4. 返回 "这是 location /test/2.html"

请求 /test/2.html 的执行流程

1. 匹配 ^/test/2.html → 重写为 /test/3.html
2. break 标志 → 不重新搜索 location
3. 继续执行当前 location
4. 添加 X-Rewrite-Status 头
5. 返回 "最终 URI: /test/3.html"

2. redirect 和 permanent 的区别

维度redirectpermanent
HTTP 状态码302301
含义临时重定向永久重定向
浏览器缓存不缓存会缓存
SEO 影响权重不转移权重转移到新 URL
适用场景临时维护、A/B测试域名变更、结构调整

三、rewrite 执行顺序

1. 在 server 块中的执行顺序

server {
    listen 80;
    server_name example.com;

    # 第一阶段:server 块中的 rewrite
    rewrite ^/old/(.*)$ /new/$1;
    rewrite ^/new/(.*)$ /final/$1;

    location / {
        # 第二阶段:location 块中的 rewrite
        rewrite ^/final/(.*)$ /done/$1 last;
    }
}

执行顺序

  1. 先执行 server 块中的 rewrite(按配置顺序)
  2. 然后匹配 location
  3. 最后执行 location 中的 rewrite

2. rewrite 的循环处理

Nginx 对 rewrite 有循环限制(默认 10 次),防止死循环:

# ❌ 错误示例:会导致循环
server {
    rewrite ^/(.*)$ /$1;  # 自己重写自己

    # 错误日志会显示:
    # "rewrite or internal redirection cycle"
}

# ✅ 正确示例:加条件避免循环
server {
    if ($uri !~ ^/static/) {
        rewrite ^/(.*)$ /static/$1 last;
    }
}

四、高级应用场景

场景1:基于条件的智能重写

server {
    listen 80;
    server_name www.example.com;

    set $do_rewrite 0;

    # 条件1:旧版本文档
    if ($request_uri ~ "^/docs/v1/(.*)") {
        set $do_rewrite 1;
        set $new_path "/docs/current/$1";
    }

    # 条件2:移动端访问
    if ($http_user_agent ~* "(mobile|android|iphone)") {
        set $do_rewrite 2;
        set $mobile_version "1";
    }

    # 条件3:特定时间段(配合外部脚本)
    if ($time_iso8601 ~ "^(.*)T(09|10|11):") {
        set $do_rewrite 3;
        set $traffic_type "peak";
    }

    # 根据条件执行重写
    if ($do_rewrite = 1) {
        rewrite ^(.*)$ $new_path last;
    }

    if ($do_rewrite = 2) {
        rewrite ^(.*)$ /mobile/$1 last;
    }

    if ($do_rewrite = 3) {
        rewrite ^(.*)$ /peak/$1 last;
    }
}

场景2:多语言站点重写

server {
    listen 80;
    server_name example.com;

    # 根据浏览器语言重写
    set $lang "en";  # 默认英文

    if ($http_accept_language ~* "^zh") {
        set $lang "zh";
    }

    if ($http_accept_language ~* "^ja") {
        set $lang "ja";
    }

    # 如果 URL 没有语言前缀,自动添加
    if ($request_uri !~ "^/(zh|en|ja)/") {
        rewrite ^/(.*)$ /$lang/$1 last;
    }

    location / {
        root /var/www/$lang;
        index index.html;
    }
}

场景3:SEO 友好 URL 重写

server {
    listen 80;
    server_name blog.example.com;

    # 文章URL:/post/123/hello-world
    # 实际处理:/article.php?id=123&title=hello-world
    location /post/ {
        rewrite ^/post/(\d+)/([^/]+)/?$ /article.php?id=$1&title=$2 last;
    }

    # 分类分页:/category/tech/page/2
    # 实际处理:/list.php?cat=tech&page=2
    location /category/ {
        rewrite ^/category/([^/]+)/page/(\d+)/?$ /list.php?cat=$1&page=$2 last;
    }

    # 标签:/tag/nginx
    # 实际处理:/tag.php?tag=nginx
    location /tag/ {
        rewrite ^/tag/([^/]+)/?$ /tag.php?tag=$1 last;
    }

    # 日期归档:/2025/01
    # 实际处理:/archive.php?year=2025&month=01
    location ~ ^/(\d{4})/(\d{2})/?$ {
        rewrite ^ /archive.php?year=$1&month=$2 last;
    }

    # 分页:/page/2
    # 实际处理:/index.php?page=2
    location /page/ {
        rewrite ^/page/(\d+)/?$ /index.php?page=$1 last;
    }
}

场景4:防盗链图片重写

server {
    listen 80;
    server_name images.example.com;

    location ~* \.(jpg|png|gif)$ {
        # 允许的域名
        valid_referers none blocked example.com *.example.com;

        if ($invalid_referer) {
            # 盗链图片重写为警告图片
            rewrite ^(.*)$ /images/anti-theft.jpg last;
        }

        root /data/images;
        expires 30d;
    }

    # 防止警告图片被重写
    location = /images/anti-theft.jpg {
        root /data/images;
        expires -1;
    }
}

场景5:临时维护与流量切换

server {
    listen 80;
    server_name shop.example.com;

    # 维护开关(通过文件存在控制)
    set $maintenance_mode 0;

    if (-f /var/www/maintenance.on) {
        set $maintenance_mode 1;
    }

    # 排除特定 IP(管理员)
    if ($remote_addr ~ "192\.168\.1\.(100|101|102)") {
        set $maintenance_mode 0;
    }

    # 排除健康检查
    if ($http_user_agent ~* "health-check") {
        set $maintenance_mode 0;
    }

    # 维护模式开启
    if ($maintenance_mode = 1) {
        rewrite ^(.*)$ /maintenance.html redirect;
    }

    # A/B 测试流量切分
    split_clients "${remote_addr}${http_user_agent}" $backend_group {
        10%    "new_version";    # 10% 用户到新版
        *      "old_version";    # 90% 用户到老版
    }

    location / {
        if ($backend_group = "new_version") {
            rewrite ^(.*)$ /new$1 break;
        }

        proxy_pass http://backend_$backend_group;
    }
}

五、rewrite 与 return 的选择

场景推荐用 rewrite推荐用 return
简单域名跳转return 301 http://new.com$request_uri;
路径结构重组rewrite ^/old/(.*)$ /new/$1 permanent;
HTTPS 跳转return 301 https://$host$request_uri;
伪静态rewrite ^/article-(\d+).html$ /show.php?id=$1 last;
临时维护rewrite ^ /maintain.html redirect;return 302 /maintain.html;
复杂条件✅ 可结合 if 使用❌ 无法实现

六、性能优化建议

1. 减少 rewrite 数量

# ❌ 多个 rewrite
rewrite ^/a.html /b.html;
rewrite ^/b.html /c.html;
rewrite ^/c.html /d.html;

# ✅ 一次完成
rewrite ^/a.html /d.html;

2. 优先使用 return

# ❌ rewrite 重定向
rewrite ^/(.*)$ http://new.com/$1 permanent;

# ✅ return 更高效
return 301 http://new.com$request_uri;

3. 合理使用 last 和 break

# 不需要重新匹配时用 break
location /download/ {
    rewrite ^/download/(.*)\.(exe|msi)$ /dl.php?file=$1.$2 break;
    # 后续指令仍执行
    proxy_pass http://download_backend;
}

# 需要进入新 location 时用 last
location / {
    rewrite ^/old/(.*)$ /new/$1 last;
    # 这里的 proxy_pass 不会执行
    proxy_pass http://old_backend;
}

七、调试技巧

server {
    listen 80;
    server_name test.com;

    location /debug {
        # 添加调试头
        add_header X-Original-URI $request_uri;
        add_header X-Rewritten-URI $uri;
        add_header X-Rewrite-Status "executed";

        # 返回变量信息
        return 200 "
        Original URI: $request_uri
        Current URI: $uri
        Args: $args
        ";
    }

    # 测试各种 rewrite
    location /test/ {
        rewrite ^/test/(\d+).html$ /debug?rewrite=yes&id=$1 last;
    }
}

访问 /test/123.html 就能看到完整的 rewrite 效果

发表回复

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