Laravelのセッションは、設定ファイルの1行で保存先が丸ごと入れ替わります。ローカルではファイルに置いたまま動いていたものが、サーバを2台に増やした瞬間にログインが切れ始める、というトラブルの多くはこの設定を置き去りにしたことが原因です。この記事では2026年8月30日時点の公式ドキュメントと配布版を実測し、設定値が何を決めているのか、file・database・redisをどの条件で選び分けるのか、有効期限とCSRFトークンがどうつながっているのか、複数台構成で何を揃えるのかを順に整理します。すでに419が出ていて原因を切り分けたい場合は、Invalid CSRF tokenの意味と直し方|Laravel 419・Django対応のほうが手順書として使えるので、そちらを先に見てください。
まとめ:既定値・ドライバ選定・複数台構成の判断
- 13系の既定ドライバは
databaseです。8〜10系のfile既定を前提にした解説記事とは出発点が違うので、古い手順をそのまま持ち込むと噛み合いません。 - 保存先を決めるのは
config/session.phpのdriver1本で、残りの設定は寿命とCookieの振る舞いを決める補助です。 - 複数台構成に上げる条件は単純で、全サーバから同じデータが読めるかどうかだけです。
fileとcookieはこれを満たしません。 - CSRFトークンはセッションの中に置かれます。セッションが先に消えればトークンも消えるので、419の多発は寿命設定の問題として読むほうが早い場面があります。
- 13系のCSRFミドルウェアは
Sec-Fetch-Siteヘッダによるオリジン検証を先に走らせ、通らなければ従来のトークン検証へ落ちる二層構成に変わりました。 - 受託案件での分岐は「台数」と「セッションへの書き込み頻度」の2軸で決まると判断しています。1台なら
databaseで十分で、Redisを足すのは書き込みが詰まってからで間に合うでしょう。
セッションはconfig/session.phpの設定1本で保存先が決まる
HTTPは前のリクエストを覚えていません。ログイン状態や入力途中のフォームを次へ引き継ぐために、サーバ側へ値を置いてCookieの識別子とひも付ける仕組みがセッションです。フレームワーク非依存の設計論はセッション管理の基礎へ譲り、ここではLaravelの実装に絞ります。Laravelは保存先を差し替え可能にしており、アプリ側のコードは書き換えずに済みます。
保存先はconfig/session.phpのdriverで切り替える
設定はconfig/session.phpに集約されています。実際に配布されている13系のファイルを開くと、ほぼすべてのキーが環境変数から読む形になっており、.envだけで挙動を変えられる構造です。
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_TABLE=sessions
SESSION_CONNECTION=
SESSION_COOKIE=myapp_session
SESSION_DOMAIN=
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax
13系のデフォルト値はdriver=database、lifetime=120(分)、expire_on_close=false、encrypt=false、table=sessions、same_site=lax、http_only=true、partitioned=falseという並びでした。lotteryだけは環境変数を持たず[2, 100]が直書きされており、これは古いセッションを掃除する確率を表します。100回のリクエストにつき2回、期限切れレコードの削除処理が走る、という意味です。
serializationがjsonになっている点も見落とされがちなところでしょう。PHPのシリアライズ形式ではなくJSONで保存されるため、セッションにオブジェクトをそのまま突っ込むと復元時に配列へ落ちます。値オブジェクトを詰めたい誘惑に駆られたら、IDだけを置いて取り出し側で再構築する形へ倒してください。
13系の既定はdatabaseでsessionsテーブルを先に作る
ここが古い記事との最大の食い違いです。Laravel 8から10のころ、既定はfileでした。11以降はdatabaseが既定になり、13系でもそのままです。日本語の解説記事はfile前提で書かれたものがまだ上位に残っているので、手順をコピーして進めると「なぜかテーブルが要ると言われる」状態になります。
テーブルは新規プロジェクトなら初期マイグレーションに含まれています。既存プロジェクトへ後から入れる場合は、専用のArtisanコマンドでマイグレーションを生成してください。
php artisan make:session-table
php artisan migrate
なお、このコマンド名は11系でsession:tableから改称されたものです。手元の版で通らないときはphp artisan listで名前を確認するのが早いでしょう。ローカル環境をDockerで組んでいるなら、Laravel Sailとは|sail up/downコマンドの意味と使い方・導入手順のとおりsail artisan越しに叩く形になります。
読み書きはsessionヘルパとリクエスト経由の2通りで書ける
値の出し入れには2つの入口があります。コントローラの引数にIlluminate\Http\Requestを型宣言して$request->session()を辿る書き方と、グローバルのsession()ヘルパを呼ぶ書き方です。テスト時の検証方法まで含めて両者に実質差はないので、プロジェクト内で揃っていれば構いません。
// リクエスト経由
$value = $request->session()->get('cart_id', 'default');
$request->session()->put('cart_id', $cart->id);
// ヘルパ経由
$value = session('cart_id');
session(['cart_id' => $cart->id]);
// 次のリクエストまでだけ残す
$request->session()->flash('status', '保存しました');
// 破棄と再生成
$request->session()->forget('cart_id');
$request->session()->regenerate();
覚えておきたいのはregenerate()とinvalidate()の役割分担です。前者は識別子だけを振り直して中身を残し、後者は振り直しと全データ削除を同時に行います。ログイン成功時にセッション固定攻撃を潰すのは前者、ログアウト時が後者という切り分けです。スターターキットやFortifyを入れていれば認証時の再生成は自動で走ります。
file・database・redisの3ドライバをどう選び分けるか
ドライバは6系統ありますが、受託案件で候補に挙がるのはfile・database・redisの3つです。残りは用途がはっきりしていて迷う余地がありません。
fileドライバは単一サーバで完結する開発環境までの選択肢です
fileはセッションをstorage/framework/sessions配下のファイルとして書き出します。追加の準備が要らないので開発機では手軽ですが、本番ではサーバを増やせない一点で行き詰まるでしょう。古いファイルが数十万件たまり、掃除の処理自体が重くなる問題も付いてきます。
NFSなどの共有ストレージを噛ませれば理屈上は回避できます。ただしファイルロックの挙動がストレージ実装に依存するため、同時書き込みでデータが飛ぶ経路が残ります。共有マウントでfileを複数台に配る構成は、動いているように見えて再現性の低い不具合を抱え込むだけなので採りません。
databaseは追加のミドルウェアなしで複数台構成に耐えられる
databaseはセッションをsessionsテーブルの1行として持ちます。すでにあるRDBを共有ストアとして使い回せるため、ミドルウェアを1つも増やさずに複数台構成へ上げられるのが利点です。13系で既定になったのもこの汎用性が理由でしょう。
気をつけるのは書き込み回数です。セッションはリクエストごとに更新されるため、画面遷移の多いアプリではRDBへのUPDATEがアクセス数と同じだけ飛びます。先回りしてRedisを立てる必要はありません。
読み書きを分離した構成を採っている場合は、セッションだけ書き込み側の接続へ固定してください。レプリカ遅延の下でセッションを読むと、直前に書いた値が見えない現象が起きます。SESSION_CONNECTIONで接続名を指定できるので、専用の接続を定義しておくと切り分けが楽になります。
redisドライバは書き込みが多い画面を抱えるときに効いてくる
redisはメモリ上のキーとして持つため、書き込みの重さが問題になった段階での移行先になります。使うにはPhpRedis拡張をPECLから入れるか、predis/predisをComposerで足すかのどちらかが前提です。接続先はSESSION_CONNECTIONか設定ファイルのconnectionで指定します。
キャッシュと同じRedisインスタンスを共用するときは、データベース番号を分けておくことをおすすめします。キャッシュを丸ごと消す運用をしているとログイン中の利用者が全員ログアウトする、という事故が起きるためです。設定キャッシュとの関係やクリアコマンドの使い分けはLaravelのキャッシュクリア|cache:clearとoptimize:clearの違いと本番デプロイの手順にまとめてあります。
永続化の設定も確認しておきましょう。RDBと違い、Redisはプロセス再起動でデータが消える構成にもできます。再起動でログアウトする前提かどうかを、インフラ側と先に合意してください。
主要な4ドライバの保存先と前提条件と向く規模を一覧にして比べる
| ドライバ | 保存先 | 前提 | 向く構成 |
|---|---|---|---|
| file | storage配下のファイル | 追加設定なし | 単一サーバの開発環境 |
| cookie | 暗号化されたCookie | 容量4KBの制限 | 極小データのみ |
| database | sessionsテーブル | マイグレーション | 複数台構成の標準解 |
| redis | Redis上のキー | 拡張かpredis | 書き込みが多い画面 |
| memcached | メモリ上のキー | 拡張の導入 | 揮発を許せる共有 |
| dynamodb | DynamoDBの項目 | AWS資格情報 | サーバレス寄りの構成 |
| array | PHPの配列 | 永続化しない | テスト実行時 |
arrayはテスト用でリクエストをまたいで残りません。
セッションの有効期限とCSRFトークンが切れる仕組みを押さえる
「ログインが勝手に切れる」「フォーム送信で419が出る」という報告は、別々の現象に見えて同じ設定値から派生している場合があります。
寿命はlifetimeとexpire_on_closeとlotteryで決まる
lifetimeは分単位の生存時間で、既定は120分です。この時計は最終アクセスからの経過で測られるため、操作を続けている限り延びていきます。逆に言えば、2時間放置したタブから送信ボタンを押すと期限切れに当たります。
expire_on_closeをtrueにすると、Cookieの有効期限がブラウザ終了時になります。共用端末の業務システムでは有効ですが、利用者には「タブを閉じただけでログアウトする」挙動に映るので、要件として明示してから入れてください。
lotteryは掃除の確率です。databaseやfileのように自動失効しない保存先では、期限切れレコードがこの抽選に当たったリクエストで消えます。アクセスの少ないシステムでは掃除が走らずsessionsテーブルが肥大するので、スケジューラで定期削除を回すほうが確実でしょう。
CSRFトークンはセッションに載るので期限切れが419応答になる
CSRFトークンはセッションの中に保存されます。フォームに埋め込まれた_tokenと、サーバ側のセッションが持つトークンを突き合わせて検証する仕組みなので、セッションが先に消えれば照合相手が存在しなくなり、419が返ります。
ここから実務的な結論が2つ出ます。1つは419の多発を「CSRFの設定ミス」ではなく「セッションが想定より早く消えている」と読む視点。もう1つは、入力に時間がかかる長大なフォームではlifetimeを延ばすか、トークンを取り直す仕掛けを入れることです。トークンはセッションIDの再生成でも変わるため、ログイン直後に別タブの古いフォームを送ると419になるのは設計どおりの挙動になります。原因別の切り分け手順はInvalid CSRF tokenの意味と直し方|Laravel 419・Django対応で扱っています。
13系の新しいミドルウェアはSec-Fetch-Siteを先に確認する
13系でCSRF保護の実装がPreventRequestForgeryとして整理され、検証が二層になりました。まずブラウザが送るSec-Fetch-Siteヘッダを見て同一オリジンからの要求だと判定できればトークン検証を省き、判定できなければ従来どおりトークンを照合します。このクラスがどの層で走り、登録先や実行順序をどう決めるかはLaravelのミドルウェアとは|適用範囲の使い分けと実行順序の決め方に整理しました。
注意点として、Sec-Fetch-SiteはHTTPS接続でしかブラウザが送りません。HTTPで動かしている環境ではオリジン検証が働かず、常にトークン検証へ落ちます。挙動が版で変わったように見えたら、まず接続を確かめてください。
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
// オリジン検証のみに寄せる(不一致は403)
$middleware->preventRequestForgery(originOnly: true);
// サブドメイン間の要求を許す
$middleware->preventRequestForgery(allowSameSite: true);
// Webhookなどを除外する
$middleware->preventRequestForgery(except: [
'stripe/*',
]);
})
originOnly: trueに寄せた場合、検証に落ちた要求は419ではなく403で返る点も押さえておきましょう。監視やログの条件式を419だけで組んでいると、切り替えた瞬間に検知が漏れます。
ロードバランサ配下の複数台構成でセッションを共有する設計の勘所
1台で動いていたシステムを2台に増やすとき、セッションは最初に引っかかる箇所です。ここを設計として片付けておくと、台数を増やす作業がただの設定変更になります。
スティッキーセッションに頼らず共有ストアへ寄せる設計を基本にする
ロードバランサの機能で同じ利用者を同じサーバへ固定する方法はあります。ただしこれは配分の偏りを生み、サーバを1台落とすとその上の利用者だけがログアウトする構造を作るので、共有ストアへ寄せるほうを既定にしてください。
移行そのものはSESSION_DRIVERの書き換えとマイグレーションだけで、アプリ側のコードは触りません。切り替えた時点で既存のセッションは引き継がれず全員がログアウトするため、深夜帯の実施か告知を前提に計画してください。
ワーカーを常駐させる実行方式を併用している場合は、リクエストをまたいで状態が残る性質と組み合わさります。セッションから読んだ値をシングルトンへ保持すると別の利用者へ漏れる経路ができるので、Laravel Octaneとは|ワーカー常駐の仕組みと状態リーク・採用条件で挙げた注意点と併せて確認してください。
Cookieのdomainとsecureとsame_siteの3点を揃える
保存先を共有しても、識別子を運ぶCookieの設定がずれていれば結果は同じです。
SESSION_DOMAINはCookieを送る対象ドメインです。サブドメインをまたいで共有したいときだけ先頭にドットを付けた親ドメインを指定し、不要なら空のままにしてください。広く指定するほど、意図しないサブドメインへ識別子が届く経路が増えます。
SESSION_SECURE_COOKIEはHTTPS接続でのみCookieを送る設定で、本番ではtrueにします。SESSION_SAME_SITEは既定のlaxで大半は足りますが、外部サイトからPOSTで戻る決済連携ではnoneが要る場合があり、secureの有効化が前提になります。SPAから叩く構成でのCookieの扱いはLaravel Sanctumとは:SPAとモバイルのトークン認証とCookieセッションの使い分けのほうが詳しいので、そちらに寄せて設計してください。
同時リクエストによる書き込みの取りこぼしはblockで抑え込む
既定では、同じセッションからの複数リクエストが並行して走ります。画面から同時に2本のXHRを投げ、両方がセッションへ書き込むと、後から終わったほうが前の書き込みを上書きします。頻度は低いものの、原因の見えない値の消失として報告されがちな現象です。
Route::post('/profile', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10);
ルート定義にblockを鎖状に付けると、同じ識別子を持つ要求が順番待ちになります。第1引数がロックを保持する上限秒数、第2引数が待機する上限秒数で、どちらも省略時は10秒です。待っても取得できなければLockTimeoutExceptionが投げられます。
使える保存先は原子ロックに対応したものに限られ、memcached・dynamodb・redis・database・file・arrayが対象です。cookieドライバでは使えません。全ルートに付けると同時実行性を落とすので、競合する画面に絞って適用してください。
受託案件でセッションドライバを決めるときの判断基準と見送り条件
ここまでを実際の案件でどう決めるかという形に落とします。判断を分けるのは技術的な優劣ではなく構成と業務要件です。
採用条件はサーバの台数とセッション書き込み頻度の2軸で切り分ける
1台構成で当面増やす予定がないならdatabaseで決まりです。すでにRDBがあるのだから増えるものは何もなく、複数台へ上げる日が来ても設定を触らずに済みます。fileのほうが速いという理屈は成り立ちますが、その差が体感に出る規模なら先にほかの箇所が詰まっているはずです。
複数台構成が確定していて、かつ管理画面のように1画面あたりの遷移が多いシステムなら、最初からredisを前提に見積もってください。判断の目安として、同時接続が数百に達してセッション更新がRDBの書き込みの上位に顔を出すようなら移行時期だと考えています。逆に、社内の数十人が使う業務システムでこの水準に届くことはまずありません。
cookieドライバは、サーバ側に何も持たない構成を作れる一方で採用しません。理由は容量制限と、保存内容が利用者の手元にある前提の設計を強いられることです。暗号化されるとはいえ、業務データの置き場としては筋が悪いでしょう。
共有ストレージにfileを置いて複数台で読ませる構成も見送ります。ロックの挙動がストレージ側の実装に左右され、障害時の再現が取れないためです。同じ工数ならdatabaseへ寄せるほうが安く済みます。
もう1つ、セッションに大きな配列や検索結果を丸ごと詰める設計も避けてください。保存先がどれであれリクエストごとに全体が読み書きされるので、サイズがそのまま応答時間へ乗ります。一時的な値の置き場が要るなら、セッション単位に隔離されるキャッシュAPIを使うか、明示的なキャッシュ層へ逃がすほうが素直です。こうした基盤側の設計を含めて業務システムを組み立てる場面では、業務用・Webアプリ開発のように認証まわりとインフラ構成をまとめて引き受ける形で相談いただくと、後から効いてくる判断を最初に固められます。
運用に組み込むのはconfigキャッシュとGCと監視の3点です
本番では設定がキャッシュされるため、.envを書き換えただけでは反映されません。デプロイ手順にキャッシュの作り直しを組み込んでおいてください。ここを忘れると、切り替えたはずのドライバが古いままという状態が起きます。
databaseを選んだ場合はsessionsテーブルの件数を監視対象に入れておきましょう。lotteryによる掃除はアクセス量に比例するため、夜間バッチ主体のシステムでは想定より溜まります。件数の急増は寿命設定のミスを知らせる指標にもなります。
版の追随も運用の一部です。13系は2026年3月17日に出て、バグ修正が2027年第3四半期まで、セキュリティ修正が2028年3月17日までという期限が公表されています。12系はバグ修正の期限が2026年8月13日で切れ、セキュリティ修正のみが2027年2月24日まで残る段階に入りました。セッションとCSRFはセキュリティ修正が直接届く領域なので、期限の手前で移行計画を立ててください。
よくある質問
SESSION_DRIVERを変えたのに反映されないのはなぜですか?
本番で設定がキャッシュされている場合、.envの書き換えだけでは効きません。設定キャッシュを作り直してから確認してください。
ログインが数時間で切れるのを延ばすにはどうしますか?
SESSION_LIFETIMEの値を分単位で延ばします。既定は120分です。ただし共用端末で使うシステムでは放置時のリスクが上がるため、長時間の維持が必要なら「ログイン状態を保持する」機能を別に用意するほうが筋が通ります。
sessionsテーブルが増え続けるのは放置してよいですか?
期限切れレコードはlottery設定の確率で掃除されるため、アクセスの少ないシステムでは溜まります。件数が数十万に届くようならスケジューラで定期削除を回してください。
RedisとDBのどちらを選ぶか迷ったら何を見ますか?
すでにRedisを運用しているならredis、そうでなければdatabaseから始めてください。新規にRedisを立てる判断は、セッション更新がDBの負荷として観測されてからで間に合います。
APIだけのアプリでもセッションの設定は要りますか?
トークン認証だけで完結するなら、セッションを使う経路自体がありません。ただしSPAをCookieベースで認証する構成では、セッションとCSRFの設定がそのまま効いてきます。どちらの方式を採るかで前提が変わるので、認証方式を先に決めてください。
関連記事
- Invalid CSRF tokenの意味と直し方|Laravel 419・Django対応:419が出たあとの原因別の切り分け手順です。
- Laravel Sanctumとは:SPAとモバイルのトークン認証とCookieセッションの使い分け:認証方式を先に決めるための材料です。
- Laravelのキャッシュクリア|cache:clearとoptimize:clearの違いと本番デプロイの手順:設定の反映とキャッシュ運用の前提です。
- Laravel Octaneとは|ワーカー常駐の仕組みと状態リーク・採用条件:常駐実行時の状態の扱いを扱っています。
- Laravel Sailとは|sail up/downコマンドの意味と使い方・導入手順:ローカル環境でコマンドを叩く前提です。