Tommy 碎碎念

Tommy Wu's blog

« 上一篇 | 下一篇 »

Nginx/FastCGI/PHP-FPM 相關設定
post by tommy @ 02 十二月, 2011 16:48

上個月中開始, 逐步將家裡機器裡頭的 Apache/mod_php 改成 Nginx/FastCGI/PHP-FPM 來運作. 經過了近兩個星期的嚐試, 目前看起來應該是可以完整的取代了.

以下是在 Debian Stable (Squeeze) 的安裝與設定.

  • PHP-FPM

PHP 5.3.3 之後, 這功能已經被 PHP 官方納入, 所以應該不用再自行 patch 並編譯. 不過... 雖然 Debian Squeeze 的 PHP 已經是 5.3.3 的版本, 但是並沒有把 php-fpm 給編入, 所以.... 我們還是要自己處理. 可以把 testing 裡的 php 5.3.8 source 抓回來自行編譯就可以用了.

安裝 php5-fpm 之後, 設定檔在 /etc/php5/fpm/ 底下, 基本上只要修改 /etc/php5/fpm/pool.d/www.conf 就可以了. 我是把 listen 由 tcp 改成 socket file 來使用. 另外, 可以指定不同的使用者來執行 php, 所以, 我把 www.conf 複製成 tommy.conf, 然後把 裡頭的 www 換成 tommy, 也把 user 設成 $pool (也就是 tommy), 把 listen 指定成 /var/run/php-fpm/$pool.sock (要自己建立該目錄). 這樣子重新執行後, 就會多幾個用 tommy 這個使用者執行的程式出現. 這樣子我們可以在後面的 nginx 中, 讓使用者使用自己的 user 來跑 php (就像 suphp 的作法一樣), 而不是強迫所有的人都用 httpd 的使用者來跑 php. (差別在於對於使用者本身的目錄有讀寫的權限, 如果用 httpd 的使用者來跑的話, 寫入檔案或上傳檔案之後的使用者會是 httpd 的使用者, 會有權限的問題)

另外, 如果有需要修改 php.ini 的話, 記得是去改 /etc/php5/fpm/php.ini 才可以. (後頭會提到有個地方要改)

  • Nginx

在 Debian Squeeze 的 Nginx 好像是 0.7.x 版的, 要直接用也可以. 如果要新版的, 可以用 Nginx 本身提供的版本 (stable 版本, 目前是 1.0.10), Nginx 官方下載網站的底下有安裝的說明. 另外, 在 Debian 的 backports 網站, 也有提供新版的 Nginx (用的是 development 的版本, 1.1.0, PS. Nginx 最新的 development 版本是 1.1.10).

把 nginx 裝起來後, 應該只要去修改 /etc/nginx/nginx.conf 裡頭有關 php 的那一段後, 執行 /etc/init.d/nginx reload 就應該可以執行 php 了. (對於沒有 rewrite 處理過, 能正確找到應該有的檔案的情形下, 應該都可以用)

聽起來很簡單吧.... 不過有些小地方我們要注意一下.

預設的設定方式可能像這樣:

location ~ \.php$ {
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm.sock
fastcgi_index index.php;
include fastcgi_params;
}

就直接把有 .php 的 request 轉給 fastcgi 的 server (一般來說, 如果沒有非本機連線的需求時, 用 socket file 的方式應該會比較好).

不過這個方式並不能正確處理 PATH_INFO, 所以如果你的 php 程式有用到 PATH_INFO 時, 就會有問題.

而 PATH_INFO 的處理, 可以有兩種方式來處理, 一種是交給 php 本身來處理, 只要在 php.ini 把 cgi.fix_pathinfo 設成 1, 然後 nginx 中的設定改成這樣:

location ~ \.php {
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param PATH_INFO $fastcgi_script_name;
}

把 location .php 後頭的 $ 移除, 然後加上如上頭的 PHP_INFO 參數. 這樣子 php 本身會產生正確的 PATH_INFO 與 PATH_TRANSLATED 變數. 不過... 當沒有 PATH_INFO 時, 上頭這個設定會有點不太正確, 有些程式可能反而不能正常運作 (如 Cacti), 所以, 我們可以把兩種設定都放上去, 像是:

location ~ \.php$ {
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ \.php {
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param PATH_INFO $fastcgi_script_name;
}

先設定 .php$, 再設定 .php 這樣就應該可以了.

不過目前 php 本身的處理會有安全上的問題 (google 找一下應該可以看到一些例子), 所以... 不建議由 php 來處理 PATH_INFO, 而改用 nginx 來處理.

如果要使用 nginx 來處理的話, 請將 php.ini 的 cgi.fix_pathinfo 設為 0, 再把 cgi.discard_path 設成 1 (這參數多數系統的 php.ini 裡頭都沒有, 自己加上去吧, 這是 5.3.3 之後才有的參數, 如果不設為 1, 在我的測試環境中, 有 PATH_INFO 的情況都會出現 404 的錯誤). 然後使用這樣子的設定方式:

location ~ \.php {
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
}

不過, 這樣子處理, 在沒有 PATH_INFO 時, 會被 PATH_INFO 設成空字串 (但仍有這變數), 這時...  PHP_SELF 也會被設成空的, 對某些使用到 PHP_SELF 的程式可能會有問題 (如 Cacti). 比較合理的方式應該是去檢查 $fastcgi_path_info 是不是空的, 如果不是才去設定 PATH_INFO 與 PATH_TRANSLATED 的值. 不過... 很可惜, nginx 並不能在 if 裡頭去設定 fastcgi_param, 所以... 沒辦法這樣子來設定. 所以... 跟上頭一樣, 我們也可以加上另一個不用 PATH_INFO 的設定, 如:

location ~ \.php$ {
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ \.php {
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
}

這樣子應該不管有沒有 PATH_INFO 都可以正常使用. 不過... 就是要寫兩段... 所以, 我用另一種方式如:

location ~ \.php {
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
set $p $fastcgi_path_info;
if ($p = "") {
set $p $fastcgi_script_name;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $p;
fastcgi_param PATH_TRANSLATED $document_root$p;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
}

把空的 PATH_INFO 設成 $fastcgi_script_name, 這樣子在我的系統上看起來運作也是正常的. (不知道有沒有錯, 也許寫成兩段才是比較合理的方式)

接著... 前面有提到我們可以讓不同的使用者, 以他們的身份來執行 php, 所以... 例如:

location ~ \.php {
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
set $p $fastcgi_path_info;
if ($p = "") {
set $p $fastcgi_script_name;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $p;
fastcgi_param PATH_TRANSLATED $document_root$p;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm/tommy.sock;
fastcgi_index index.php;
}

如果是 tommy 這個使用者, 就去使用 tommy 的那一個 socket file.

不過... 如果有很多使用者, 就表示每一個的設定都有單獨去設... 這樣如果要改, 可能會很麻煩,  所以... 我們可以把加上一個變數來使用, 把上頭的設定獨立成一個叫 php.conf (放 /etc/nginx/conf/ 目錄下) 的檔案, 這樣在每一個使用者的設定, 只要先去指定變數再去 include conf/php.conf 就可以.

location ~ \.php {
uninitialized_variable_warn off;
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
set $p $fastcgi_path_info;
if ($p = "") {
set $p $fastcgi_script_name;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $p;
fastcgi_param PATH_TRANSLATED $document_root$p;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/php-fpm/$cur_php_user.sock;
fastcgi_index index.php;
}

先用 uninitialized_variable_warn off 把使用到沒初始化的變數警告關閉, 然後把 fastcgi_pass 的值加上變數來處理.

server {
# for user tommy
set $cur_php_user tommy;
include conf/php.conf;
}

這樣子在該使用者的主機設定用這方式來處理就可以了.

不過... 這時有個問題, 如果並沒有在 php-fpm 裡頭去跑那個使用者的 php 程式時, 或忘了設定 $cur_php_user 時... 就沒有那個 socket file, 這時... 執行就會有問題.

比較合理的方式應該像是:

location ~ \.php {
uninitialized_variable_warn off;
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
set $p $fastcgi_path_info;
if ($p = "") {
set $p $fastcgi_script_name;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $p;
fastcgi_param PATH_TRANSLATED $document_root$p;
#fastcgi_pass 127.0.0.1:9000;
set $fpm_url unix:/var/run/php-fpm.sock;
if (-f /var/run/php-fpm/$cur_php_user.sock) {
set $fpm_url /var/run/php-fpm/$cur_php_user.sock;
}
fastcgi_pass $fpm_url;
fastcgi_index index.php;
}

檢查該 socket file 是否存在, 若不存在則用系統原本的那一個.

這樣看起來很完美吧. 不過... 上頭的方式是無法運作的, 因為 -f 會用 S_ISREG() 來檢查檔案是否存在, 而 socket file 用這方式查會不存在, 等於是永遠不會用該使用者的身分來執行 php.

所以... 要不就是修改 nginx 的原始碼, 加上 S_ISSOCK() 的處理, 或... 退一步, 我們檢查 php-fpm 的設定檔, 如:

location ~ \.php {
uninitialized_variable_warn off;
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
set $p $fastcgi_path_info;
if ($p = "") {
set $p $fastcgi_script_name;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $p;
fastcgi_param PATH_TRANSLATED $document_root$p;
#fastcgi_pass 127.0.0.1:9000;
set $fpm_url unix:/var/run/php-fpm.sock;
if (-f /etc/php5/fpm/pool.d/$cur_php_user.conf) {
set $fpm_url /var/run/php-fpm/$cur_php_user.sock;
}
set $cur_php_user "";
fastcgi_pass $fpm_url;
fastcgi_index index.php;
}

這樣子, 除非加了 php-fpm 的設定檔, 但忘了重跑 php-fpm, 否則應該不會有出現找不到 fastcgi backend 的情形.

不過, 以往使用 apache 的時候, 可以讓不同的附檔名如 .php, .php3, .phtml, .suphp 都當成是 php 來執行. 這時, 在 nginx 上頭, 要怎麼處理呢?

由於 nginx 的設定都是用正規表示式來處理, 所以, 原本很簡單的想法, 就是直接把各種檔名都加上, 如:

location ~ \.(php|php3|phtml|suphp) {
uninitialized_variable_warn off;
include fastcgi_params;
fastcgi_split_path_info ^((?U).+\.(php|php3|phtml|suphp))(/?.+)$;
set $p $fastcgi_path_info;
if ($p = "") {
set $p $fastcgi_script_name;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $p;
fastcgi_param PATH_TRANSLATED $document_root$p;
#fastcgi_pass 127.0.0.1:9000;
set $fpm_url unix:/var/run/php-fpm.sock;
if (-f /etc/php5/fpm/pool.d/$cur_php_user.conf) {
set $fpm_url /var/run/php-fpm/$cur_php_user.sock;
}
set $cur_php_user "";
fastcgi_pass $fpm_url;
fastcgi_index index.php;
}

看起來似乎沒錯, 不過... 由於用到了 fastcgi_split_path_info, 而這個後頭只接受傳回兩個值的正規表示式, 所以上頭的方式, 就不被 nginx 所接受. 這時... 我們只能選擇把每一種附檔名寫一段設定來處理 (這樣實在不是個好方法), 或者.. 不要用 fastcgi_split_path_info 來處理, 自行用正規表示式來處理, 如:

location ~ ^(?<SCRIPT>/.*\.(php|php3|phtml|suphp))(?<PATH_INFO>.*)$ {
uninitialized_variable_warn off;
if ($PATH_INFO = "") {
# avoid empty path_info
set $PATH_INFO $SCRIPT;
}
set $fpm_url unix:/var/run/php-fpm.sock;
if (-f /etc/php5/fpm/pool.d/$cur_php_user.conf) {
set $fpm_url unix:/var/run/php-fpm/$cur_php_user.sock;
}
set $cur_php_user "";
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param PATH_INFO $PATH_INFO;
fastcgi_param PATH_TRANSLATED $document_root$PATH_INFO;
fastcgi_param SCRIPT_NAME $SCRIPT;
fastcgi_param SCRIPT_FILENAME $document_root$SCRIPT;
fastcgi_pass $fpm_url;
}

這樣子, 就能對多種附檔名都放在同一個設定裡頭了.

 


2011/12/10:
這星期試著測試 Gallery3, 發現這樣的設定還是會造成問題, 所以比較好的作法, 還是把它分成兩段來處理. 如:

 

location ~ ^(?<SCRIPT>/.*\.(php|php3|phtml|suphp))$ {
uninitialized_variable_warn off;
set $fpm_url unix:/var/run/php-fpm.sock;
if (-f /etc/php5/fpm/pool.d/$cur_php_user.conf) {
set $fpm_url unix:/var/run/php-fpm/$cur_php_user.sock;
}
set $cur_php_user "";
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_NAME $SCRIPT;
fastcgi_param SCRIPT_FILENAME $document_root$SCRIPT;
fastcgi_pass $fpm_url;
}
location ~ ^(?<SCRIPT>/.*\.(php|php3|phtml|suphp))(?<PATH_INFO>.*)$ {
uninitialized_variable_warn off;
if ($PATH_INFO = "") {
# avoid empty path_info
set $PATH_INFO $SCRIPT;
}
set $fpm_url unix:/var/run/php-fpm.sock;
if (-f /etc/php5/fpm/pool.d/$cur_php_user.conf) {
set $fpm_url unix:/var/run/php-fpm/$cur_php_user.sock;
}
set $cur_php_user "";
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param PATH_INFO $PATH_INFO;
fastcgi_param PATH_TRANSLATED $document_root$PATH_INFO;
fastcgi_param SCRIPT_NAME $SCRIPT;
fastcgi_param SCRIPT_FILENAME $document_root$SCRIPT;
fastcgi_pass $fpm_url;
}

 

 

Del.icio.us Furl HEMiDEMi Technorati MyShare
commons icon [1] Re:Nginx/FastCGI/PHP-FPM 相關設定 [ 回覆 ]

感謝您鉅細靡遺的闡釋觀念與實務上的解說,受益良多

小弟也是想要停用apache 而移轉到 nginx 上去。我所習慣的語言是ruby on rails,坦言之這方面的設定資料真的是不多。

花了一些功夫終於把rails的程式給請上去nginx 了,但我目前是保留著apache 跟 phpmyadmin,我是想要把phpmyadmin 也給請過去。

不過。 找到的資料泰半不完整。 我用的作業系統是ubuntu 11.10
不知您是否有相關的資料。 像是如何在ubuntu 上安裝php-fpm 的
麻煩您了。 再次感謝您

commons icon [2] Re:Nginx/FastCGI/PHP-FPM 相關設定 [ 回覆 ]

不知道 11.10 是基於 debian 那一個時期的版本做的.
php-fpm 應該在 5.3.3 會納入 php 中 (不過 debian 的 squeeze 5.3.3 的 php 並沒有把這個編入).
我知道是是 woody 裡的 php 5.3.8 就有 php-fpm...

迴響
暱稱:
標題:
個人網頁:
電子郵件:
迴響

  

Bad Behavior 已經阻擋了 89 個過去 7 天試圖闖關的垃圾迴響與引用。
Power by LifeType. Template design by JamesHuang. Valid XHTML and CSS