XserverでハマったPHPセッションの話し

― メインドメイン × サブドメイン × セッション競合という地雷 ―

起きたこと(概要)

Xserverの環境で、
PHPセッション自体は動いているのに、ページ遷移やPOSTを跨ぐと session_id() が毎回変わる
という不可解な現象に遭遇しました。

  • ローカル環境では正常
  • セッションファイルも保存されている
  • Cookieも送信されている
  • それでもセッションが維持されない

原因が見えづらく、かなり時間を取られたトラブルです。


環境の特徴(伏せ字)

  • メインサイト:example.org
  • サブドメイン:app.example.org
  • 両方とも同一レンタルサーバー上
  • PHPの標準セッション(ファイル保存)
  • session.use_strict_mode = 1

一見すると「サブドメインはメインの配下にあるだけ」に見えますが、
Cookieとセッションの世界では、これは完全に別アプリ扱いになります。


症状

  • セッションは開始される
  • セッションファイル(sess_xxx)も作成される
  • しかしPOST時や次ページで 別の session_id が発行される
  • CSRFトークンやログイン判定が常に失敗する

デバッグしてみると、リクエストヘッダにこんな状態が出ていました。

Cookie: PHPSESSID=aaaa...; PHPSESSID=bbbb...

同じ名前の Cookie が2つ送られている


真の原因

1. メインとサブで「同じセッション名」を使っていた

  • メインサイト:PHPSESSID
  • サブドメイン:PHPSESSID

Domain / Path / 設定差により、
同名のセッションCookieが複数発行されて共存していました。

2. ブラウザは両方送る

ブラウザはルール通り、条件に合うCookieをすべて送信します。
PHP側はその中から1つを拾いますが、どれを拾うかは安定しません

3. strict_mode がトドメを刺す

session.use_strict_mode=1 の場合、

  • 渡されたIDに対応するセッションファイルが見つからない
  • → 不正ID扱い
  • 新しい session_id を再発行

結果として
「セッションは動くが、維持されない」 という地獄が完成します。


なぜローカルでは再現しない?

  • ドメインが単純
  • Cookieが1枚しか存在しない
  • レンタルサーバー特有の save_path / Cookie挙動がない

つまり、本番特有の事故でした。


解決策(最短・確実)

サブドメインを「完全に別アプリ」として扱う

① セッション名を分ける

session_name('APPSESSID'); // サブドメイン専用

② Cookieをホスト限定にする

session_set_cookie_params([
  'lifetime' => 0,
  'path'     => '/',
  'domain'   => '', // ホスト限定
  'secure'   => true,
  'httponly' => true,
  'samesite' => 'Lax',
]);

③ save_path は必ず明示指定

レンタルサーバーでは
phpinfo()の表示を信用しない

ini_set('session.save_path', '/full/path/to/session');

④ 過去の Cookie を全削除

  • メインドメイン
  • サブドメイン
  • 親ドメイン共有分

これをやらないと「亡霊Cookie」で再発します。


学び(重要)

  • サブドメインは別世界「のつもり」で設計すると痛い目を見る
  • セッションを共有しないなら、名前を分けるのが最強
  • Cookieが2枚来ていたら、PHPの挙動は信用できない
  • レンタルサーバーでは
    「表示されている設定」と「実体」が違うことがある

まとめ

この手のトラブルは、

  • PHPの仕様
  • ブラウザのCookie仕様
  • レンタルサーバー独自挙動

この3点をまたがないと原因に辿り着けません。

正直かなりやっかいですが、
一度理解すれば再発は防げるタイプの地雷でもあります。