
本文详解 `.htaccess` 中常见重写规则导致的 url 无限嵌套问题,通过修正 rewritecond 与 rewriterule 的逻辑顺序和匹配条件,实现安全、可靠的 php 文件无扩展名访问(如 `/page` → `/page.php`),同时阻止直接访问 `.php` 后缀并防止重定向循环。
在 Apache 环境中,许多开发者希望隐藏 PHP 文件扩展名以提升 URL 美观性与安全性(例如将 https://example.com/about.php 显示为 https://example.com/about)。但若 .htaccess 规则设计不当,极易引发无限重定向循环——正如问题中所示:浏览器地址栏持续拼接路径(/mv/project/mv/project/...),最终触发 500 错误或超时。
根本原因在于原始规则存在逻辑冲突与执行顺序缺陷:
- 第一组规则 RewriteRule ^(.*)$ $1.php 会将所有请求(包括已带 .php 的内部重写)无差别追加 .php;
- 而第二组规则 RewriteCond %{THE_REQUEST} ... 本意是屏蔽直接访问 .php 的请求,但它在重写链中执行过晚,无法阻止第一组规则对已重写路径的二次处理;
- 更关键的是,^(.*)$ 是贪婪通配符,当 $1.php 对应的文件实际存在时,Apache 会再次匹配该规则(因为重写后的 URI 仍满足 ^(.*)$),从而形成死循环。
✅ 正确做法是:严格区分“内部重写”与“外部重定向”,并确保规则具备终止性与排他性。以下是经过生产环境验证的安全配置:
Options +FollowSymlinks
RewriteEngine On
# Step 1: 阻止用户直接访问 .php 文件(返回 404)
RewriteCond %{THE_REQUEST} \.php[\s?] [NC]
RewriteRule ^(.+)\.php$ - [R=404,L]
# Step 2: 将无后缀请求映射到对应 .php 文件(仅当文件真实存在时)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^(.+)$ $1.php [L]? 关键说明:
立即学习“PHP免费学习笔记(深入)”;
- [R=404,L] 中的 L(Last)标志确保匹配后立即终止规则链,避免后续规则干扰;
- 第二组规则末尾的 [L] 同样不可或缺——它防止 Apache 在内部重写后重新遍历全部规则;
- RewriteCond %{REQUEST_FILENAME}.php -f 精确判断物理文件是否存在,杜绝对不存在路径的无效重写;
- 使用 ^(.+)$ 替代 ^(.*)$ 可避免匹配空路径(如根目录 /),进一步提升健壮性。
⚠️ 注意事项:
- 切勿在未验证文件存在性的情况下进行重写(即避免 .* 直接映射),这是循环的主因;
- 如需支持首页(如 / → /index.php),可额外添加:
RewriteCond %{REQUEST_URI} ^/$ RewriteCond %{DOCUMENT_ROOT}/index.php -f RewriteRule ^$ index.php [L] - 启用 RewriteLog(Apache
- 部署前务必在子目录或开发环境充分测试,避免影响线上服务。
总结:URL 重写不是“越简短越安全”,而是精准匹配 + 显式终止 + 条件前置的组合艺术。遵循上述结构,即可在保持语义清晰的同时,彻底规避无限重定向风险,让无扩展名路由真正可靠、可维护。











