limitinternalrecursion 不能防止重写死循环,它只在死循环发生后强制中断并报错。 它是 apache 的安全熔断机制,不是预防手段。真正要防死循环,得靠规则设计和日志观测。
limitinternalrecursion 是什么,为什么它不“防止”循环
这个指令设置的是内部重定向(internal redirect)和子请求(subrequest)的嵌套深度上限,默认值是 10。当 mod_rewrite 触发的重写链超过该次数,apache 就会中止处理,并记录类似 [alert] mod_rewrite: maximum number of internal redirects reached 的错误——这说明循环已经发生了,只是被拦下了。
它不分析规则逻辑,也不提前拦截;就像汽车的安全气囊,撞上了才弹出来。
- 它控制两个维度:第一个数字是内部重定向链长度(即重写跳转次数),第二个是子请求嵌套深度(如 mod_dir 查 directoryindex 时的递归)
- 只写一个数字(如 limitinternalrecursion 20)会同时设为两者
- 该指令作用域为 server config、virtual host 或 directory,不能在 .htaccess 中使用
- 调高它(比如设成 20)仅用于临时定位问题,绝不能作为“解决死循环”的方案
死循环场景示例
# ❌ 危险配置:无条件重写导致无限循环 rewriteengine on rewriterule ^(.*)$ /index.php?page=$1 [l] # 问题:/index.php 匹配 ^(.*)$,再次重写 → 死循环
limitinternalrecursion 指令详解
# 默认配置(apache 2.4+)
limitinternalrecursion 10
# 生产环境建议(根据应用复杂度调整)
limitinternalrecursion 5
# 调试时临时增大(配合 rewriteloglevel)
<ifdefine debug>
limitinternalrecursion 20
</ifdefine>| 参数值 | 行为 | 适用场景 |
|---|---|---|
| 0 | 禁用重写(不推荐) | 仅静态资源服务器 |
| 1-3 | 严格限制 | 简单路由,无嵌套重写 |
| 5-10 | 平衡配置(默认) | 现代 cms/框架(wordpress/laravel) |
| 20+ | 宽松限制 | 复杂多级代理/遗留系统 |
与 mod_rewrite 协同的完整方案
1. 基础防护配置
<virtualhost *:80>
servername example.com
documentroot /var/www/html
# 核心:限制内部递归深度
limitinternalrecursion 5
# 辅助:限制单请求子请求数(防止间接循环)
limitrequestfields 50
limitrequestfieldsize 8190
<directory /var/www/html>
rewriteengine on
# 关键:使用 [end] 标志(apache 2.4+)替代 [l]
# [l] 仅停止当前轮,[end] 完全终止重写处理
rewriterule ^api/(.*)$ /api/index.php?path=$1 [end]
# 传统 [l] 需配合条件避免循环
rewritecond %{env:redirect_status} ^$
rewriterule ^(.*)$ /index.php [l]
</rewritecond>
</directory>
</virtualhost>2. 复杂路由的安全模式
<directory /var/www/app>
rewriteengine on
# 防御层1:严格递归限制
limitinternalrecursion 3
# 防御层2:标记已重写请求(双重保险)
rewritecond %{env:redirect_status} 200
rewriterule ^ - [l]
# 防御层3:排除已处理 uri 模式
rewritecond %{request_uri} !\.php$
rewritecond %{request_uri} !^/(assets|uploads)/
rewriterule ^(.*)$ /router.php?uri=$1 [qsa,l]
# 错误处理:递归超限返回 500
errordocument 500 /error/loop-detected.html
</directory>3. 与 proxy 组合时的特殊处理
# 反向代理场景易触发多级重写
<virtualhost *:443>
sslengine on
# 代理场景需更大递归深度(子请求+重写)
limitinternalrecursion 10
rewriteengine on
# 关键:排除代理目标路径,防止回环
rewritecond %{request_uri} !^/proxy-backend/
rewriterule ^/api/(.*)$ /proxy-backend/api/$1 [p,l]
proxypass /proxy-backend/ http://localhost:8080/
proxypassreverse /proxy-backend/ http://localhost:8080/
# 代理错误时停止递归
rewritecond %{http:upgrade} websocket [nc]
rewriterule ^/ws/(.*)$ ws://localhost:8081/$1 [p,l]
</virtualhost>关键关联指令对比
| 指令 | 作用层级 | 防护目标 | 与 limitinternalrecursion 关系 |
|---|---|---|---|
| limitinternalrecursion | 服务器/虚拟主机/目录 | 重写/映射死循环 | 核心防线 |
| limitrequestfields | 服务器 | http 头溢出 | 辅助防护 |
| rewriteoptions maxredirects | 目录 | 外部重定向次数 | 仅限制外部 301/302,不防内部重写 |
| limitrequestbody | 服务器/目录 | 请求体大小 | 防止大请求导致的间接递归 |
死循环检测与调试
日志分析配置
# 开发/调试环境启用详细重写日志
<ifdefine debug_rewrite>
loglevel rewrite:trace8
# 自定义日志格式包含递归深度
logformat "%h %l %u %t \"%r\" %>s %b \"%{redirect_status}e\" %{rewrite_uri}n" rewrite_log
customlog logs/rewrite.log rewrite_log
</ifdefine>典型死循环特征
# 错误日志中的循环迹象
[rewrite:trace4] ... (1) init rewrite engine
[rewrite:trace4] ... (1) applying pattern '^/(.*)$' to uri 'index.php'
[rewrite:trace4] ... (2) init rewrite engine <-- 数字递增表示递归
[rewrite:trace4] ... (2) applying pattern '^/(.*)$' to uri 'index.php'
...
[rewrite:trace1] ... (11) init rewrite engine <-- 超过 limitinternalrecursion
[core:error] ... request exceeded the limit of 10 internal redirects
due to probable configuration error. use 'limitinternalrecursion'
to increase the limit if necessary.现代框架的安全模板
# laravel / symfony / 类似 mvc 框架
<directory /var/www/laravel/public>
rewriteengine on
# 严格递归限制(框架路由已处理多级逻辑)
limitinternalrecursion 2
# 发送所有请求到 index.php(单次重写)
rewritecond %{http:authorization} .
rewriterule .* - [e=http_authorization:%{http:authorization}]
rewritecond %{request_filename} !-d
rewritecond %{request_filename} !-f
rewriterule ^ index.php [end] # [end] 完全终止,比 [l] 更安全
</directory>
# wordpress 典型配置
<directory /var/www/wordpress>
rewriteengine on
limitinternalrecursion 3
rewritebase /
rewriterule ^index\.php$ - [l]
rewritecond %{request_filename} !-f
rewritecond %{request_filename} !-d
rewriterule . /index.php [l]
</directory>关键最佳实践
- 优先使用 [end] 标志(apache 2.4+):完全终止重写处理,比 [l] 更防循环
- 始终设置 rewritecond %{env:redirect_status} ^$:排除已重写请求
- 递归深度宁小勿大:生产环境建议 3-5,异常时增大而非默认无限
- 配合 rewritemap 复杂逻辑:避免在 rewriterule 中堆砌条件导致隐性循环
核心要点:limitinternalrecursion 是最后防线,良好设计的重写规则应通过 [end] 标志和 redirect_status 条件自我防循环,而非依赖递归限制。
到此这篇关于apache通过limitinternalrecursion指令防止重写死循环的文章就介绍到这了,更多相关apache limitinternalrecursion防止重写死循环内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论