許多開發者在配置 Nginx + PHP 時,習慣直接搜尋教程然後複製貼上配置。聽起來省時省力,但網路上流傳的許多資料已經年久失修、漏洞百出。如果不求甚解地照單全收,遲早會為此付出代價。本文將帶你深入理解 Nginx 配置的繼承機制、指令行為,並提供一份經過優化、更安全、更易維護的配置範本。
假設專案採用 PHP 實現前端控制器(統一入口):所有 PHP 請求都導向同一個檔案(如 index.php),再由該檔案解析 REQUEST_URI 實現路由。這是現代 PHP 框架(Laravel、Symfony、Yii 等)的標準做法。
許多教程會給出如下配置:
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 指令放錯位置,導致重複定義在問題配置中,index 定義在 location / 內部。Nginx 配置採用「從外到內」的繼承關係:http → server → location。多個 location 是平級關係,不會互相繼承。若未來新增其他 location(如 location /api/),就必須重複寫 index。正確做法是將 index 放在 server 區塊,讓所有 location 自動繼承。
if 指令,違背 Nginx 聲明式本質if (!-e $request_filename) {
rewrite . /index.php last;
}
if 是 rewrite 模組的指令,並非核心指令。Nginx 配置是聲明式而非過程式的,if 與非 rewrite 模組指令混用時,行為往往違背直覺。這裡的邏輯——「檔案不存在則轉發到入口」——正是 try_files 指令的設計用途:
try_files $uri $uri/ /index.php$is_args$args;
這樣更簡潔、高效,且避免了 if 的潛在陷阱。
fastcgi_params vs fastcgi.conf:硬編碼風險Nginx 提供兩個 FastCGI 參數檔:fastcgi_params 和 fastcgi.conf。兩者差異極小,唯一不同是後者多了一行:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
(注意:變數之間沒有斜線)
早期許多教程教導在 location 內硬編碼 SCRIPT_FILENAME(如 /path$fastcgi_script_name),這不僅不靈活,還容易出錯。為了規範用法,官方引入 fastcgi.conf 並建議直接 include fastcgi.conf;。
為什麼不直接修改舊檔?因為 fastcgi_param 是陣列型指令:同級多次使用會新增而非替換。若同級定義兩次 SCRIPT_FILENAME,兩者都會傳給後端,可能導致未預期行為。引入新檔案可避免此類問題。
cgi.fix_pathinfo 安全隱患當 PHP 開啟 cgi.fix_pathinfo=1(預設開啟)時,若請求 /upload/avatar.jpg/foo.php 且 avatar.jpg 存在,PHP 可能將其解析為 PHP 腳本執行。若 Nginx 與 PHP 部署在同一台伺服器,最簡單的防禦是在 PHP location 內加入:
try_files $uri =404;
確保傳給 PHP-FPM 的腳本檔案確實存在。
綜合上述分析,給出改良版本:
server {
listen 80;
server_name foo.com;
root /path;
index index.html index.htm index.php; # 放在 server 區塊,所有 location 繼承
location / {
# 統一入口:嘗試檔案 -> 目錄 -> index.php 帶參數
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
# 安全過濾:防止 cgi.fix_pathinfo 導致的任意代碼執行
try_files $uri =404;
include fastcgi.conf; # 使用標準配置,自動處理 SCRIPT_FILENAME
fastcgi_pass 127.0.0.1:9000; # 或 unix socket: unix:/run/php/php8.1-fpm.sock
# fastcgi_index index.php; # 已由 fastcgi.conf 內部處理,可省略
}
}
這份配置結構清晰、指令職責單一、安全性更高,且易於擴展維護。掌握這些核心原理,你就能根據專案實際需求靈活調整,而不再盲目複製貼上。