目录

Nginx 中 location 块内 if 指令失效问题

问题描述

访问 https://example.com/usr/life_sqlite.db 时,请求一直返回 302 重定向,最终导致 “Maximum redirects followed” 错误。

配置文件 /etc/angie/rewrite/typecho.conf 原内容:

location / {
  if ($uri ~* \.db$) {
    return 403;
  }
  if (!-e $request_filename) {
    rewrite ^(.*)$ /index.php$1 last;
  }
}

location /usr/ {
  location ~* \.db$ {
    deny all;
    return 403;
  }
}

根本原因

Nginx 的 “if is evil” 问题

Nginx 的 if 指令与 location 块组合使用时,行为不可预测。这是 Nginx 长期存在的已知问题,官方文档甚至警告 “if is evil”。

当请求 /test.db 时的执行流程:

  1. 请求匹配 location /
  2. 理论上:if ($uri ~* \.db$) 应该返回 403
  3. 实际上:if 内的 return 没有生效
  4. 继续执行后续的 rewrite 规则
  5. 文件不存在 → rewrite 到 /index.php/test.db → 触发 PHP 处理 → 重定向循环

尝试过的失败方案

  1. 使用 location ^~ /usr/ 前缀匹配 → 失败
  2. location ~* \.db$ 放在 location / 之前 → 失败
  3. 使用 set 变量 + if 判断 → 失败
  4. 使用 break 关键字 → 失败
  5. 直接在 server 块中添加 location ~* \.db$ → 失败

最终解决方案

使用 error_page 技巧配合命名 location,绕过 if 指令的执行问题:

location / {
  error_page 418 =403 @db_deny;

  if ($uri ~* \.db$) { return 418; }

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

location @db_deny {
  return 403;
}

测试结果

$ curl -sI "https://example.com/test.db"
HTTP/2 403

$ curl -sI "https://example.com/usr/life_sqlite.db"
HTTP/2 403

关键知识点

  1. Nginx location 匹配优先级

    • = 精确匹配
    • ^~ 前缀匹配(不检查正则)
    • ~~* 正则匹配
    • 普通前缀匹配
  2. if 指令的局限性

    • location 块内的 if 指令行为不可预测
    • 尽量避免在 location 内使用复杂 if 条件
    • 优先使用 error_page 或独立的 location 块
  3. error_page 技巧

    • 使用不存在的状态码(如 418)作为中间跳转
    • 配合命名 location(@name)实现条件分支
    • 可以在 rewrite 阶段之后生效

参考资料