在 Ubuntu/Debian 使用 apt 安裝 PHP 時,開發者常遇到以下 Notice:
PHP Notice: session_start(): ps_files_cleanup_dir: opendir(/var/lib/php5) failed: Permission denied (13)
本文從 PHP 核心原始碼層面剖析 Session GC(垃圾回收)觸發機制、權限問題成因,並提供多種解決方案。
PHP 採用機率式 GC:每次 session_start() 皆有機率啟動清理。核心判斷邏輯(簡化自 ext/session/session.c):
int nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C));
if (nrand < PS(gc_probability)) {
PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &nrdels TSRMLS_CC);
}
gc_probability:分子,預設 1gc_divisor:分母,預設 100php_combined_lcg():產生 0~1 隨機浮點數換算機率:1/100 = 1%,即約每 100 次請求觸發 1 次 GC。
apt 安裝的 PHP 預設 Session 儲存路徑 /var/lib/php5 權限為 drwx-wx-wt(1733,sticky bit):
PHP Worker 通常以 www-data 身分執行,屬於「其他人」。正常寫入 Session 檔案無問題(因有寫入權限),但 GC 需要 opendir() 讀取目錄列表以掃描過期檔案,因而觸發 Permission denied。
系統預設透過 cron 定期清理,而非依賴 PHP 內建 GC:
# /etc/cron.d/php5 09,39 * * * * root [ -x /usr/lib/php5/maxlifetime ] \ && [ -d /var/lib/php5 ] \ && find /var/lib/php5/ -type f -cmin +$(/usr/lib/php5/maxlifetime) -print0 \ | xargs -n 200 -r -0 rm
每小時第 9、39 分鐘以 root 權限執行,完美繞過權限問題。
; php.ini session.gc_probability = 0
完全禁用機率觸發,完全依賴系統 cron 清理,零效能負擔、零 Notice。
# 讓 www-data 可讀取目錄 chmod 1777 /var/lib/php5 # 或更改擁有者 chown www-data:www-data /var/lib/php5
⚠️ 降低目錄權限可能引入安全風險(如共享主機環境),生產環境謹慎評估。
; php.ini session.save_path = "/var/www/sessions"
建立專屬目錄並給予 PHP 讀寫權限,隔離系統預設目錄。
session.save_handler = redis session.save_path = "tcp://127.0.0.1:6379"
徹底規避檔案系統權限問題,並獲得效能與分散式優勢。
此 Notice 並非 Bug,而是「機率式 GC」與「系統預設 cron 清理」共存下的副作用。理解 gc_probability/divisor 機制與目錄權限模型,即可選擇最適合的解決策略。生產環境建議採用方案一或四,簡單且穩健。