想通過 Apache Rewrite 直接禁止 .php 後綴訪問,卻發現內部重寫也會觸發規則導致 403 死循環。本文剖析遞歸機制,提供環境變量判斷、PHP 端攔截、文件系統權限三種可行方案。
# ❌ 錯誤配置
RewriteEngine On
RewriteRule ^test$ /test.php [L] # 外部 /test → 內部 /test.php
RewriteRule ^test\.php$ - [F,L] # 直接訪問 .php → 403
# 實際流程:
# 1. 請求 /test → 匹配規則1 → 內部重定向到 /test.php
# 2. 內部重定向再次觸發 Rewrite 引擎
# 3. 匹配規則2 → 返回 403 Forbidden
# 結果:/test 也無法訪問!
RewriteEngine On
RewriteRule ^test$ /test.php [L]
# 只有非內部重定向的直接請求才有 REDIRECT_STATUS 為空
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule \.php$ - [F,L]
原理:內部重定向後,Apache 設置 REDIRECT_STATUS=200,直接請求該變量不存在/為空。
在統一入口文件或自動預加載文件中:
<?php
// 只允許內部重定向/框架路由訪問,直接請求 .php 返回 404
if (php_sapi_name() !== 'cli') {
$realPath = realpath($_SERVER['SCRIPT_FILENAME'] ?? '');
$docRoot = realpath($_SERVER['DOCUMENT_ROOT'] ?? '');
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
// 檢查是否直接請求 .php 文件(非重寫、非入口文件)
$isDirectPhp =
str_ends_with($requestUri, '.php') &&
!$realPath || strpos($realPath, $docRoot) !== 0; // 簡化判斷
// 更可靠:檢查 REDIRECT_URL (Apache) 或 REDIRECT_STATUS
if (empty($_SERVER['REDIRECT_URL']) && empty($_SERVER['REDIRECT_STATUS'])) {
// 直接請求 .php
http_response_code(404);
exit('Not Found');
}
}
?>
配合框架單一入口(index.php),所有業務 .php 文件放在 public/ 外部,Web 根目錄只暴露 index.php + 靜態資源,從源頭杜絕直接訪問。
/var/www/project/
├── public/ # Web DocumentRoot
│ ├── index.php # 唯一入口
│ ├── .htaccess # Rewrite 規則
│ └── assets/ # 靜態資源
├── src/ # PHP 源碼(不可直接訪問)
│ ├── Controller/
│ ├── Model/
│ └── ...
├── vendor/
└── config/
Apache/Nginx DocumentRoot 指向 public/,PHP 文件物理隔離在外部,無需任何 Rewrite 規則 即可防止直接訪問。
| 方案 | 優點 | 缺點 | 推薦場景 |
|---|---|---|---|
| REDIRECT_STATUS 判斷 | 純配置、無代碼侵入 | Apache 專用、調試困難、易漏配 | legacy 項目快速修復 |
| PHP 端攔截 | 跨 Web 服務器、邏輯可控、可自定義響應 | 需修改代碼、入口文件需放行 | 現代框架項目(Laravel/Symfony/ThinkPHP) |
| 文件系統隔離 | 物理級安全、零配置、最佳實踐 | 需調整目錄結構、部署流程 | 所有新項目強制標準 |
location ~ \.php$ {
# 只有通過內部重寫到 @php 才執行
internal; # 關鍵:標記為內部 location,外部無法直接匹配
fastcgi_pass php-fpm;
include fastcgi_params;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location @php {
# 內部重寫目標
fastcgi_pass php-fpm;
include fastcgi_params;
}
internal 指令完美解決 Nginx 下的同類問題。