症状別 /
ログインセッションがすぐ切れる ─ WordPress・Laravel・業務システム別の原因と対処
ログインセッションがすぐ切れる症状の原因は、PHPのセッション設定・ロードバランサのスティッキーセッション不整合・SameSite/Secure属性の誤設定・WordPress固有の鍵再生成・JWTのリフレッシュ機構の欠如など多岐にわたる。DevToolsのCookie確認とphp.iniの実値確認を起点に切り分ける手順を解説。
<p>ログインしたのに数分後には弾かれている。あるいは、ページを切り替えるたびに認証が外れる。こうした「セッション切れ」は原因が一箇所に収まることはなく、Webサーバ・PHPランタイム・アプリケーション・ブラウザの4層が絡み合う。しかもエラーメッセージが「セッションが切れました」の一行だけなので、どこを直せばいいか見当もつかない。</p>
<p>整理すると、原因は大きく5つに分類できる。</p>
<p><strong>1. PHPのセッション設定</strong></p>
<p>php.iniの<code>session.gc_maxlifetime</code>は、セッションデータをサーバ側でガベージコレクションするまでの秒数だ。デフォルトは1440秒(24分)。これが短すぎると、タブを開いたまま少し席を外した後に弾かれる。<code>session.cookie_lifetime</code>はブラウザ側のCookieの有効期限で、0に設定するとブラウザを閉じるまで有効になる。この2つは連動して設定しないとちぐはぐになる。<code>gc_maxlifetime</code>を3600にしても<code>cookie_lifetime</code>が0のままなら、ブラウザを開き続ける限りはCookieが残るが、サーバ側のセッションデータは別のタイミングで消えることもある。</p>
<p>共用サーバでありがちなのが、<code>session.save_path</code>が複数サイトで共有されているケースだ。セッションファイルが他サイトのGCで消される可能性があり、設定値が正しくても実害が出る。<code>/tmp/sessions/mysite</code>のような専用ディレクトリを指定して隔離するのが正しい。</p>
<p><strong>2. ロードバランサとスティッキーセッションの不整合</strong></p>
<p>複数台のWebサーバを並べている環境で、セッションをファイルで持っている場合、リクエストが別のサーバに振られた瞬間にセッションが切れる。スティッキーセッション(セッションIDで同一サーバに固定するALBの設定)を有効にするか、RedisやMemcachedにセッションを移すかの2択になる。前者は応急処置、後者が本質的な解決だ。</p>
<p>LaravelであればSESSION_DRIVERを<code>redis</code>に変えるだけで切り替わる。ただし<code>redis.conf</code>の<code>maxmemory-policy</code>が<code>allkeys-lru</code>のままだと、メモリ逼迫時にセッションデータが追い出される。<code>volatile-lru</code>にしてセッションキーにTTLを付けておく方が安全だ。</p>
<p><strong>3. SameSite属性とリバースプロキシ</strong></p>
<p>Chrome 80以降、Cookieのデフォルト<code>SameSite</code>属性が<code>Lax</code>になった。POSTリクエストでサードパーティコンテキストからCookieが送られなくなり、OAuth認証後にセッションが引き継がれないという問題が多発した。特にAWSのALBやCloudflareのトンネル経由でアプリを動かしていると、ドメインが複数噛む構成になりやすい。</p>
<p>Nginxがリバースプロキシとして前段にいる場合、<code>proxy_set_header X-Forwarded-Proto https</code>が抜けていると、PHPがHTTPとしてリクエストを受け取り、Secureフラグ付きのCookieが発行されない。結果として<code>SameSite=None; Secure</code>が必要な場面でSecureが落ちてCookieが送られなくなる。</p>
<p><strong>4. WordPressに固有の事情</strong></p>
<p>WordPressのログインセッションはセキュリティキーとソルト(<code>wp-config.php</code>の<code>AUTH_KEY</code>等8個の定数)に依存する。これらを再生成するとアクティブなセッションは全て無効になる。セキュリティプラグインが自動で再生成している場合、ユーザは気づかぬうちにログアウトされ続ける。</p>
<p><code>LOGGED_IN_COOKIE</code>定数でCookie名を明示的に指定している環境で、<code>WP_HOME</code>と<code>WP_SITEURL</code>が食い違っていると、Cookieのドメインスコープが合わずセッションが維持されないことがある。マルチサイトで<code>COOKIE_DOMAIN</code>を設定するケースでも同様のズレが起きる。</p>
<p><strong>5. アプリケーションレベルの設計上の問題</strong></p>
<p>LaravelのCSRF検証で419が返る場合、セッション切れと誤認されることがある。実際には<code>CSRF_COOKIE_DOMAIN</code>の設定ミスやAPPのURL設定の不一致が原因で、セッション自体は生きているのにトークン検証でリジェクトされているだけだ。</p>
<p>JWTをセッション代わりに使っているアプリで、トークンの有効期限を15分に設定してリフレッシュ機構なしで運用しているケースがある。15分操作がなければ弾かれるのは仕様通りだが、エンドユーザには「セッション切れ」としか見えない。リフレッシュトークンとsilent renewの実装か、有効期限を業務用途に合わせた時間(8時間程度)に見直す必要がある。</p>
<p>確認の順序としては、まずブラウザのDevToolsのApplicationタブでCookieの有効期限と属性(Secure/HttpOnly/SameSite)を確認する。次にサーバの<code>php -i | grep session</code>でphp.iniの実際の値を確認する。その後でアプリケーションのセッション設定に入る。この順番を飛ばすと、PHPの設定を直したつもりが実際にはvhostのphp.ini上書きで元に戻っていた、というような二度手間が起きる。</p>
<p>サーバサイドのログに残らない類の問題だけに、切り分けが地味に手間がかかる。ただ原因が特定できれば、直し方は明快だ。</p>
<p>整理すると、原因は大きく5つに分類できる。</p>
<p><strong>1. PHPのセッション設定</strong></p>
<p>php.iniの<code>session.gc_maxlifetime</code>は、セッションデータをサーバ側でガベージコレクションするまでの秒数だ。デフォルトは1440秒(24分)。これが短すぎると、タブを開いたまま少し席を外した後に弾かれる。<code>session.cookie_lifetime</code>はブラウザ側のCookieの有効期限で、0に設定するとブラウザを閉じるまで有効になる。この2つは連動して設定しないとちぐはぐになる。<code>gc_maxlifetime</code>を3600にしても<code>cookie_lifetime</code>が0のままなら、ブラウザを開き続ける限りはCookieが残るが、サーバ側のセッションデータは別のタイミングで消えることもある。</p>
<p>共用サーバでありがちなのが、<code>session.save_path</code>が複数サイトで共有されているケースだ。セッションファイルが他サイトのGCで消される可能性があり、設定値が正しくても実害が出る。<code>/tmp/sessions/mysite</code>のような専用ディレクトリを指定して隔離するのが正しい。</p>
<p><strong>2. ロードバランサとスティッキーセッションの不整合</strong></p>
<p>複数台のWebサーバを並べている環境で、セッションをファイルで持っている場合、リクエストが別のサーバに振られた瞬間にセッションが切れる。スティッキーセッション(セッションIDで同一サーバに固定するALBの設定)を有効にするか、RedisやMemcachedにセッションを移すかの2択になる。前者は応急処置、後者が本質的な解決だ。</p>
<p>LaravelであればSESSION_DRIVERを<code>redis</code>に変えるだけで切り替わる。ただし<code>redis.conf</code>の<code>maxmemory-policy</code>が<code>allkeys-lru</code>のままだと、メモリ逼迫時にセッションデータが追い出される。<code>volatile-lru</code>にしてセッションキーにTTLを付けておく方が安全だ。</p>
<p><strong>3. SameSite属性とリバースプロキシ</strong></p>
<p>Chrome 80以降、Cookieのデフォルト<code>SameSite</code>属性が<code>Lax</code>になった。POSTリクエストでサードパーティコンテキストからCookieが送られなくなり、OAuth認証後にセッションが引き継がれないという問題が多発した。特にAWSのALBやCloudflareのトンネル経由でアプリを動かしていると、ドメインが複数噛む構成になりやすい。</p>
<p>Nginxがリバースプロキシとして前段にいる場合、<code>proxy_set_header X-Forwarded-Proto https</code>が抜けていると、PHPがHTTPとしてリクエストを受け取り、Secureフラグ付きのCookieが発行されない。結果として<code>SameSite=None; Secure</code>が必要な場面でSecureが落ちてCookieが送られなくなる。</p>
<p><strong>4. WordPressに固有の事情</strong></p>
<p>WordPressのログインセッションはセキュリティキーとソルト(<code>wp-config.php</code>の<code>AUTH_KEY</code>等8個の定数)に依存する。これらを再生成するとアクティブなセッションは全て無効になる。セキュリティプラグインが自動で再生成している場合、ユーザは気づかぬうちにログアウトされ続ける。</p>
<p><code>LOGGED_IN_COOKIE</code>定数でCookie名を明示的に指定している環境で、<code>WP_HOME</code>と<code>WP_SITEURL</code>が食い違っていると、Cookieのドメインスコープが合わずセッションが維持されないことがある。マルチサイトで<code>COOKIE_DOMAIN</code>を設定するケースでも同様のズレが起きる。</p>
<p><strong>5. アプリケーションレベルの設計上の問題</strong></p>
<p>LaravelのCSRF検証で419が返る場合、セッション切れと誤認されることがある。実際には<code>CSRF_COOKIE_DOMAIN</code>の設定ミスやAPPのURL設定の不一致が原因で、セッション自体は生きているのにトークン検証でリジェクトされているだけだ。</p>
<p>JWTをセッション代わりに使っているアプリで、トークンの有効期限を15分に設定してリフレッシュ機構なしで運用しているケースがある。15分操作がなければ弾かれるのは仕様通りだが、エンドユーザには「セッション切れ」としか見えない。リフレッシュトークンとsilent renewの実装か、有効期限を業務用途に合わせた時間(8時間程度)に見直す必要がある。</p>
<p>確認の順序としては、まずブラウザのDevToolsのApplicationタブでCookieの有効期限と属性(Secure/HttpOnly/SameSite)を確認する。次にサーバの<code>php -i | grep session</code>でphp.iniの実際の値を確認する。その後でアプリケーションのセッション設定に入る。この順番を飛ばすと、PHPの設定を直したつもりが実際にはvhostのphp.ini上書きで元に戻っていた、というような二度手間が起きる。</p>
<p>サーバサイドのログに残らない類の問題だけに、切り分けが地味に手間がかかる。ただ原因が特定できれば、直し方は明快だ。</p>
この症状でお困りなら、まず無料相談
60分無料相談を予約