將 ThinkPHP 專案從 Apache 遷移至 Nginx 時,最常見的問題是 PATH_INFO 模式下的路由失效。ThinkPHP 預設依賴 /index.php/模組/控制器/方法 這類帶路徑資訊的 URL,而 Nginx 預設不將 PATH_INFO 傳給 PHP-FPM,導致框架無法正確解析路由。
if + rewrite 寫法 (舊版常見)server {
listen 80;
server_name example.com;
root /path/to/web-root;
location / {
# 核心:若檔案不存在,轉發至 index.php 並保留 PATH_INFO
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php/$1 last;
break;
}
}
location ~ \.php($|/) {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
# 手動解析 PATH_INFO
set $script $uri;
set $path_info "/";
if ($uri ~ "^(.+\.php)(/.*)") {
set $script $1;
set $path_info $2;
}
fastcgi_param PATH_INFO $path_info;
fastcgi_param SCRIPT_FILENAME /path/to/web-root$script;
}
}
缺點: 使用 if 在 location 區塊中被 Nginx 官方視為「邪惡」做法,易產生不可預期行為;邏輯分散、維護困難。
try_files + fastcgi_split_path_info (推薦)Nginx 0.7.31+ 內建 fastcgi_split_path_info 指令,專門依正則分離腳本名與 PATH_INFO,配合 try_files 取代 if,配置更乾淨、效能更好。
server {
listen 80;
server_name example.com;
root /path/to/web-root;
index index.php index.html;
location / {
# 取代 if (!-e ...) rewrite ...
# 依序嘗試:原始 URI -> /index.php$uri (帶 PATH_INFO)
try_files $uri $uri/ /index.php$uri;
}
location ~ \.php($|/) {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
# 核心:自動分離 $fastcgi_script_name 與 $fastcgi_path_info
# 正則:^(.+\.php)(/.*)$ → 群組1=腳本 群組2=PATH_INFO
fastcgi_split_path_info ^(.+\.php)(/.*)$;
# 關鍵參數傳給 PHP-FPM
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# ThinkPHP 可能需要的額外參數
fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_root/:/tmp/:/proc/";
}
}
| 指令 | 作用 | 備註 |
|---|---|---|
try_files $uri $uri/ /index.php$uri; | 依序嘗試靜態檔、目錄、最後落入 index.php 並保留完整 URI 作為 PATH_INFO | 單行取代整段 if/rewrite |
fastcgi_split_path_info ^(.+\.php)(/.*)$; | 依正則將 $uri 切分為 $fastcgi_script_name (群組1) 與 $fastcgi_path_info (群組2) | 需搭配 location ~ \.php($|/) |
fastcgi_param PATH_INFO $fastcgi_path_info; | 將解析出的 PATH_INFO 傳給 PHP ($_SERVER['PATH_INFO']) | ThinkPHP 路由核心依賴此變數 |
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | 指定實際執行的 PHP 腳本絕對路徑 | 避免寫死路徑,提升可移植性 |
cgi.fix_pathinfo:在 php.ini 設定 cgi.fix_pathinfo=0,避免安全風險並確保 PATH_INFO 解析一致性。admin.php),在 location 區塊增寫 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 即可自動適配。server 區塊加入 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 30d; access_log off; } 減輕 PHP-FPM 壓力。root 指向專案 public 或入口檔所在目錄;檢查 SCRIPT_FILENAME 路徑是否正確。PATH_INFO 未正確傳遞 → 檢查 fastcgi_split_path_info 正則與 location 匹配順序。SCRIPT_FILENAME 路徑錯誤或權限不足 → 確認 $document_root 變數與實際路徑一致,PHP-FPM 執行使用者對目錄有讀取權限。放棄 if + rewrite 的舊時代包袱,擁抱 try_files 與 fastcgi_split_path_info,不僅配置檔行數減半,更能獲得 Nginx 原生級的效能與穩定度。下次遷移 ThinkPHP、Laravel、CodeIgniter 等需 PATH_INFO 的框架時,直接套用此範本即可輕鬆上線。