典型場景:$_SESSION['captcha'] 在生成頁面已設置,提交驗證時讀取為空/未定義。
症狀:Session 文件無法寫入,每次請求生成新 Session ID,數據自然丟失。
<?php
// 診斷腳本:放入項目入口執行
echo 'save_path: ' . ini_get('session.save_path') . PHP_EOL;
echo 'session_id: ' . session_id() . PHP_EOL;
echo 'session_status: ' . session_status() . PHP_EOL; // 2=活躍
$path = ini_get('session.save_path');
// 處理 "N;/path" 格式 (分級目錄)
if (strpos($path, ';') !== false) {
list(, $path) = explode(';', $path, 2);
}
echo '實際路徑: ' . $path . PHP_EOL;
echo '目錄存在: ' . (is_dir($path) ? '是' : '否') . PHP_EOL;
echo '可寫入: ' . (is_writable($path) ? '是' : '否') . PHP_EOL;
echo '擁有者: ' . posix_getpwuid(fileowner($path))['name'] . PHP_EOL;
echo '進程用戶: ' . get_current_user() . PHP_EOL;
// 測試寫入
$testFile = $path . '/sess_test_' . random_int(1000,9999);
if (@file_put_contents($testFile, 'test') !== false) {
unlink($testFile);
echo '測試寫入: 成功' . PHP_EOL;
} else {
echo '測試寫入: 失敗 - 請檢查權限/SELinux/AppArmor' . PHP_EOL;
}
?>
解決:
mkdir -p /var/lib/php/sessions && chown www-data:www-data /var/lib/php/sessions && chmod 700 /var/lib/php/sessionschcon -R -t httpd_var_run_t /var/lib/php/sessions/tmp(系統清理任務會刪除)<?php
// ❌ 錯誤:輸出內容後才 start
echo '<html>';
session_start(); // 警告:headers already sent
// ✅ 正確:腳本最開頭,任何輸出前
session_start();
// 設置/讀取 $_SESSION
?><html>...
檢查點:所有涉及 Session 的文件**入口處**必須調用 session_start(),且無 BOM 頭、無空白輸出。
| 傳遞方式 | 常見故障 | 排查 |
|---|---|---|
| Cookie (預設) | 瀏覽器禁用 Cookie、跨域、SameSite=Lax/Strict、域名不匹配 | F12 Network 看 Set-Cookie/Request Cookie;檢查 session.cookie_domain/session.cookie_samesite |
| URL 參數 (透明 SID) | session.use_trans_sid=Off、重寫規則衝突、表單缺少隱藏域 | 啟用 ini_set('session.use_trans_sid', 1);檢查 output_add_rewrite_var() |
關鍵配置:
<?php
// 跨子域共享 Session
ini_set('session.cookie_domain', '.example.com'); // 頂級域名前加點
ini_set('session.cookie_samesite', 'Lax'); // 或 None (需 Secure)
ini_set('session.cookie_secure', true); // HTTPS 必須
ini_set('session.use_strict_mode', 1); // 防 Session Fixation
ini_set('session.use_only_cookies', 1); // 禁用 URL 傳遞 (更安全)
?>
症狀:負載均衡下,請求分發到不同節點,Session 文件只在本地磁盤。
解決方案(按推薦度):
session.save_handler = redis, session.save_path = "tcp://redis-cluster:6379?auth=pass&database=0" (需 phpredis 擴展)session_set_save_handler() 寫入 MySQL/PostgreSQLsession.save_path(性能差、單點故障,不推薦)<?php
// ❌ 常見錯誤:直接賦值空數組覆蓋
$_SESSION = []; // 或 unset($_SESSION);
// ✅ 正確銷毀特定鍵
unset($_SESSION['captcha']);
// ✅ 正確銷毀整個 Session
session_unset(); // 清空 $_SESSION
session_destroy(); // 刪除服務端文件 & 發送刪除 Cookie
// 注意:session_destroy() 後 $_SESSION 仍可讀寫,下次請求才生新 ID
?>
session.cookie_domain 預設為當前主機名。a.example.com 設置的 Cookie 默認不發送給 b.example.com。
修正:ini_set('session.cookie_domain', '.example.com'); (注意前綴點)。
session.auto_start = On 會在腳本開始前自動啟動,**禁止**在代碼中再次 session_start() (會報錯)。session.gc_maxlifetime (預設 1440s) 過短導致高併發下文件被 gc 刪除。集群環境需統一配置並由 Redis TTL 控制。session.sid_length/session.sid_bits_per_character 影響 ID 長度與熵,建議設置 32/5 (256bit)。<?php
// session_diag.php
header('Content-Type: text/plain; charset=utf-8');
session_start();
echo "=== PHP Session 診斷報告 ===\n";
echo "PHP Version: " . PHP_VERSION . "\n";
echo "SAPI: " . PHP_SAPI . "\n\n";
echo "--- 核心配置 ---\n";
foreach([
'session.save_handler','session.save_path','session.name','session.auto_start',
'session.gc_maxlifetime','session.gc_probability','session.gc_divisor',
'session.cookie_lifetime','session.cookie_path','session.cookie_domain',
'session.cookie_secure','session.cookie_httponly','session.cookie_samesite',
'session.use_strict_mode','session.use_only_cookies','session.use_trans_sid',
'session.sid_length','session.sid_bits_per_character',
] as $k) {
echo "$k = " . ini_get($k) . "\n";
}
echo "\n--- 運行狀態 ---\n";
echo "session_id: " . session_id() . "\n";
echo "session_status: " . session_status() . " (1=disabled, 2=active, 0=none)\n";
echo "COOKIE[" . session_name() . "]: " . ($_COOKIE[session_name()] ?? '未設置') . "\n";
echo "\$_SESSION 內容:\n";
print_r($_SESSION);
echo "\n--- 寫入測試 ---\n";
$_SESSION['diag_test'] = time();
echo "寫入 diag_test: " . $_SESSION['diag_test'] . "\n";
echo "重新讀取: " . ($_SESSION['diag_test'] ?? '失敗') . "\n";
$savePath = ini_get('session.save_path');
if (strpos($savePath, ';') !== false) $savePath = explode(';', $savePath, 2)[1];
echo "\n--- 文件系統 ---\n";
echo "save_path 目錄: $savePath\n";
echo "存在: " . (is_dir($savePath) ? '是' : '否') . "\n";
echo "可寫: " . (is_writable($savePath) ? '是' : '否') . "\n";
$files = glob($savePath . '/sess_*');
echo "Session 文件數: " . count($files) . "\n";
if ($files) {
$latest = max(array_map('filemtime', $files));
echo "最新修改: " . date('Y-m-d H:i:s', $latest) . "\n";
}
?>
captcha.php):session_start() → $_SESSION['captcha'] = $code → 輸出圖片,**無任何其他輸出**。session_start() → `session_start() → 取 $_SESSION['captcha'] → **大小寫不敏感比對** → 驗證後 unset($_SESSION['captcha']) (防重放)。session.cookie_path 是否覆蓋三個頁面路徑 (預設 / 通常無問題)。session.cookie_path 設置為父路徑或 /。終極建議: 生產環境**棄用文件存儲**,直接接入 Redis Session,解決 99% 併發、分布式、權限、清理問題。Laravel/Symfony/ThinkPHP 均內置支持,原生 PHP 僅需安裝 phpredis 並修改兩行 ini 配置。