許多開發者在配置 Nginx + PHP 時,習慣直接搜尋教程然後複製貼上。雖然看似省時,但網路上流傳的許多資料已年久失修、漏洞百出,盲目套用遲早會釀成災禍。本文將深入剖析常見錯誤配置,並提供經過優化的最佳實踐方案。
假設我們採用前端控制器模式(統一入口),將所有 PHP 請求導向同一個檔案,再透過解析 REQUEST_URI 實現路由。許多教程會建議如下配置:
server {
listen 80;
server_name foo.com;
root /path;
location / {
index index.html index.htm index.php;
if (!-e $request_filename) {
rewrite . /index.php last;
}
}
location ~ .php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /path$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
}
}
這段配置至少存在四個關鍵問題,我們逐一拆解。
原配置將 index 定義在 location / 內。Nginx 配置採「由外而內」的繼承模型:http → server → location。多個 location 之間屬平級關係,不會互相繼承。若未來新增 location,勢必重複定義 index。正確做法是將 index 提升至 server 區塊,讓所有 location 自動繼承。
if (!-e $request_filename) { rewrite . /index.php last; } 是典型的「If is evil」反模式。Nginx 的 if 屬 rewrite 模組指令,配置本質是聲明式而非過程式,與非 rewrite 模組指令混用極易產生非預期行為。標準替代方案是 try_files:
try_files $uri $uri/ /index.php;
語義清晰、效能更優,且避免了 if 的諸多坑點。
Nginx 提供兩份 FastCGI 參數檔:fastcgi_params 與 fastcgi.conf。兩者差異僅在於後者多了一行:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
早期許多開發者硬編碼 SCRIPT_FILENAME(如 /path$fastcgi_script_name),不利移植與維護。fastcgi_param 為陣列型指令,同層級多次定義會「新增」而非「替換」,導致後端可能收到重複參數。引入 fastcgi.conf 即為規範此行為,建議直接 include fastcgi.conf;。
當 PHP 開啟 cgi.fix_pathinfo=1 時,若請求 /upload/avatar.jpg/foo.php 且 avatar.jpg 實際存在,PHP 可能誤將其解析為 PHP 腳本。若 Nginx 與 PHP 同機,最簡單的防禦是在 location ~ \.php$ 內加入:
try_files $uri =404;
確保腳本檔案確實存在才交給 FastCGI 處理。
server {
listen 80;
server_name foo.com;
root /path;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi.conf;
fastcgi_pass 127.0.0.1:9000;
}
}
此版本結構清晰、指令位置正確、移除 if 與硬編碼、修補路徑解析漏洞。註:fastcgi_index 在此場景下已無必要,因 location 已限定 .php$;try_files 與 fastcgi_split_path_info 協同仍有邊界情況,若涉及 PATH_INFO 需額外處理,本文不再展開。