Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Laravel Session Configuration for Multi-Server

By Kokil Thapa | Last reviewed: September 2026

When you scale a Laravel application from one server to two or more behind a load balancer, users suddenly get logged out, CSRF tokens fail, and shopping carts vanish. The root cause is almost always local file-based sessions: each app node writes session data to its own disk, so the next request may land on a server that has never seen that session ID. Correct Laravel session configuration for multi-server means moving session state to a shared store every node can reach, then aligning cookie, domain, and proxy settings so the browser and every PHP worker agree on the same session. If you run enterprise Laravel applications in Nepal or anywhere else, this is one of the first production issues you hit after horizontal scaling.

Why does Laravel session configuration for multi-server matter?

Laravel stores session data separately from the session cookie the browser sends. The cookie holds an opaque session ID; the actual payload—authentication state, flash messages, form tokens—lives in whatever driver you configure in config/session.php. On a single server with the default file driver, Laravel writes to storage/framework/sessions. That works until you add a second app server.

Consider a typical setup: Nginx load balancer, two Ubuntu 24 app servers running PHP 8.5 and Laravel 13, one MySQL 9.7 database. Request one hits Server A, which creates session file abc123. Request two is routed to Server B, which looks for abc123 on its own disk, finds nothing, and starts a fresh session. The user appears logged out. The same failure mode breaks CSRF validation on POST forms, multi-step checkout flows, and admin panels using session flash data.

In my experience working on production Laravel applications behind load balancers, this is the number-one "works on staging, broken in production" report after a scale-out. Staging often runs one node; production runs two. The fix is architectural, not a code bug in your controllers.

File Sessions Fail Across NodesLoad BalancerRound-robin routingApp Server ASession abc123Local disk onlyApp Server BNo abc123 fileNew session createdResult: logout, CSRF 419, lost cartUser bounced between isolated session stores
Laravel session configuration for multi-server must replace per-node file storage with a shared backend all workers read and write.

Sessions also carry security-sensitive data. Misconfiguration can expose session fixation risks or leak cookies across subdomains. Treat session storage as part of your Laravel architecture, alongside database connections and queue workers—not an afterthought bolted on after launch.

What session driver should you use for Laravel on multiple servers?

Laravel 13 supports several session drivers via the SESSION_DRIVER environment variable. For multi-server deployments, only drivers backed by a centralised store are viable. The official Laravel session documentation lists all options; in production multi-node setups, the practical choices narrow quickly.

DriverMulti-server readyPerformanceBest for
fileNoFast on one nodeLocal dev only
cookieYesFast, size-limitedSmall session payloads only
databaseYesGood with indexingTeams already on MySQL/PostgreSQL
redisYesExcellentMost production Laravel clusters
memcachedYesExcellentExisting Memcached 1.6.x infrastructure
dynamodbYesGood on AWSServerless or AWS-native stacks

Redis is the default recommendation for most Laravel multi-server setups in 2026. Redis 8.10 handles high read/write throughput, supports TTL-based expiry that aligns with Laravel's session lifetime, and is already present on many stacks used for cache and queues. If you already run Redis for CACHE_DRIVER or Horizon, reusing the same cluster with a separate logical database index is straightforward.

Database sessions are a solid fallback when you cannot add Redis yet. Laravel ships a sessions migration via php artisan session:table. Every app node reads and writes the same MySQL 9.7 or PostgreSQL 18 table. Performance is acceptable for moderate traffic if you index the id column and run a scheduled session:gc cleanup.

Cookie driver stores the entire session in an encrypted cookie. It works across servers without shared storage, but session size is capped around four kilobytes and every request carries the full payload. Fine for simple apps; poor fit for large admin sessions or heavy flash data.

File driver must be disabled in production multi-node environments. A common mistake is copying .env from a single-server staging box where SESSION_DRIVER=file still works. I've encountered this during production deployments on sister sites sharing a Dockerized Laravel pipeline—one environment variable oversight, hours of intermittent auth failures.

When sticky sessions are not enough

Some teams enable load-balancer "sticky sessions" (session affinity) so a user's requests always reach the same node. That masks the file-session problem temporarily but creates new ones: uneven load distribution, painful deploys when a node drains, and session loss when that node dies. Sticky sessions are a bandage. Shared session storage is the cure. Use affinity only as a temporary migration crutch, not a long-term architecture.

How do you configure Redis for Laravel sessions across servers?

Redis-backed sessions are the path I recommend for most production Laravel clusters. Below is a complete, copy-pasteable configuration path for Laravel 12 or 13 on PHP 8.3+ with Redis 8.10.

Shared Redis Session StoreBrowserSession cookieLoad BalancerAny node, any requestApp Server ALaravel 13 + PHP 8.5App Server BLaravel 13 + PHP 8.5Redis 8.10Single source of session truth
Correct Laravel session configuration for multi-server routes every node to one Redis instance holding session payloads keyed by cookie ID.

Step 1: Install and verify the PHP Redis extension

Every app server needs the same PHP Redis extension. On Ubuntu with PHP 8.5:

sudo apt install php8.5-redis
php -m | grep redis
composer require predis/predis

Laravel can use either the native phpredis extension (preferred for performance) or the predis/predis package via Composer 2.10. Match what you use for cache and queues. Mixed extensions across nodes cause confusing connection errors.

Step 2: Configure .env on every app node identically

Session-related environment variables must be identical on all servers. A mismatch in SESSION_DOMAIN or APP_KEY breaks encryption and cookie scope.

SESSION_DRIVER=redis
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax

REDIS_CLIENT=phpredis
REDIS_HOST=10.0.1.50
REDIS_PASSWORD=your-redis-password
REDIS_PORT=6379
REDIS_DB=0

# Use a separate DB index for sessions vs cache
REDIS_CACHE_DB=1
REDIS_SESSION_DB=2

Point REDIS_HOST at a dedicated Redis instance or managed service reachable from every app node over a private network. Do not run Redis on localhost of each app server—that recreates the isolation problem in a different form.

Step 3: Wire config/session.php and config/database.php

In config/session.php, confirm the connection name:

'driver' => env('SESSION_DRIVER', 'redis'),
'connection' => env('SESSION_CONNECTION', 'session'),
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'encrypt' => env('SESSION_ENCRYPT', true),
'secure' => env('SESSION_SECURE_COOKIE', true),
'same_site' => env('SESSION_SAME_SITE', 'lax'),

In config/database.php, define a dedicated Redis connection for sessions:

'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'),

    'session' => [
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_SESSION_DB', 2),
    ],
],

Separating session, cache, and queue Redis databases (using numeric DB indexes 0–15 on a single Redis instance) simplifies debugging. You can inspect session keys with redis-cli -n 2 KEYS laravel_session:* without wading through cache entries. For larger deployments, use separate Redis instances entirely.

Step 4: Database driver alternative

If Redis is not available yet, switch to the database driver:

php artisan session:table
php artisan migrate

Then set SESSION_DRIVER=database. All nodes share the same MySQL or PostgreSQL connection configured in .env. Schedule garbage collection:

/* app/Console/Kernel.php or routes/console.php */
Schedule::command('session:gc')->hourly();

On a legal-tech portal with document uploads and authenticated client areas, database sessions handled moderate concurrent users reliably until traffic justified adding Redis. For high-traffic eCommerce like Nepal Gift Card, Redis is the better default from day one.

How do you set session cookies and domains correctly behind a load balancer?

Shared storage solves the server-side half. Cookie and proxy configuration solves the client-side half. Even with Redis, sessions break if cookies are scoped wrong or Laravel cannot detect HTTPS behind a reverse proxy.

Cookie and Proxy SettingsCookie scopeSESSION_DOMAIN=.example.comSECURE + SameSite=laxAPP_URL=https://example.comIdentical APP_KEY all nodesTrusted proxiesTrustHeaders middlewareX-Forwarded-For / ProtoLoad balancer IP rangeForce HTTPS in productionCommon failure signals419 CSRF token mismatchCookie set on HTTP, site on HTTPSDifferent APP_KEY per deploy node
Laravel session configuration for multi-server requires aligned cookie domain settings and trusted proxy headers on every application node.

SESSION_DOMAIN and subdomains

If your app serves app.example.com and www.example.com, set a leading-dot domain so the cookie is valid across subdomains:

SESSION_DOMAIN=.example.com

Leave SESSION_DOMAIN null for single-host apps. Never set it to a bare domain that does not match the browser URL—browsers silently reject mismatched cookies and you get phantom logouts. Test with browser devtools: confirm the laravel_session cookie appears with the expected domain, secure flag, and expiry.

Trusted proxies and HTTPS

Behind Nginx or an AWS ALB, Laravel must trust forwarded headers to know the request was HTTPS. In Laravel 11+, configure middleware in bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->trustProxies(at: '*');
})

Restrict at: to your load balancer's private IP range in production rather than wildcard. Set SESSION_SECURE_COOKIE=true and APP_URL=https://yourdomain.com on every node. Without this, Laravel may set cookies without the Secure flag, browsers may refuse them on HTTPS pages, or you get mixed-content session mismatches.

APP_KEY must be identical everywhere

Laravel encrypts session payloads when SESSION_ENCRYPT=true. Every app server must share the same APP_KEY. Deployer 7 and GitLab CI pipelines typically store .env in a shared directory outside symlinked releases—exactly the pattern used on several sites I maintain. If one node gets a regenerated key during a bad deploy, that node decrypts sessions differently and users appear logged out only on unlucky requests. After any key rotation, all sessions invalidate globally; plan that during maintenance windows.

Session lifetime and idle timeout

SESSION_LIFETIME is in minutes. One hundred twenty minutes is a common default. For client portals handling sensitive documents—like a law firm client portal—shorter lifetimes with a "remember me" option via Laravel's authentication system balance security and usability. Align Redis TTL with this value; Laravel handles expiry automatically on read for Redis and database drivers.

What common mistakes break Laravel sessions on multi-server deployments?

Knowing the correct configuration is half the job. The other half is avoiding production traps that waste debugging hours.

Session Failure DiagnosisUser logged out?Check SESSION_DRIVERMust not be fileCheck APP_KEY matchAll nodes identicalRedis reachable?Test from each nodeCookie domain OK?Secure + SameSiteStill failing? Check opcache stale configReload PHP-FPM after .env changes
Systematic diagnosis for Laravel session configuration for multi-server issues: driver, keys, Redis connectivity, cookies, then PHP-FPM cache.

Configuration drift between nodes

The most common operational failure is two app servers with different .env files. Automate environment sync through your deployment tool. With Deployer 7, keep .env in the shared directory:

set('shared_dirs', ['storage']);
set('shared_files', ['.env']);

After changing session settings, reload PHP-FPM on every node. Opcache can cache old config values if you use config:cache and forget to rebuild:

php artisan config:clear
php artisan config:cache
sudo systemctl reload php8.5-fpm

A pattern I've seen repeatedly: config cached on one node before the new SESSION_DRIVER was deployed, producing intermittent behaviour that looks like load-balancer randomness but is actually stale config.

Forgetting Redis persistence and eviction

Sessions are ephemeral, but aggressive Redis eviction under memory pressure can drop session keys mid-user-flow. Configure maxmemory-policy volatile-lru and size Redis appropriately. Monitor memory usage as part of your Ubuntu server monitoring routine. The official Redis memory optimisation guide covers eviction policies in detail.

Mixing session auth with API token auth

Multi-server session configuration applies to browser-based session guards. API routes using Laravel Sanctum bearer tokens or Passport do not depend on shared session storage for authentication—though SPA cookie authentication still does. On applications with both Blade admin panels and mobile APIs, keep the concerns separate. See Laravel API best practices for token-based patterns that avoid session coupling entirely.

CSRF and AJAX across subdomains

If your frontend JavaScript calls an API subdomain, ensure SESSION_DOMAIN and Sanctum's stateful domain list align. CSRF token mismatches (HTTP 419) after scaling out usually mean cookie scope or HTTPS detection problems, not a bad @csrf directive. Use the JSON formatter tool to inspect API responses during debugging, and verify the XSRF-TOKEN cookie is present in the browser.

Testing before you cut over traffic

Validate multi-server sessions before pointing production DNS:

  1. Deploy two app nodes with shared Redis and identical .env.
  2. Log in on node A's direct IP or hostname.
  3. Hit node B's URL with the same browser session cookie (via hosts file or direct IP with Host header).
  4. Confirm you remain authenticated and CSRF-protected forms submit.
  5. Run the same test through the load balancer with sticky sessions disabled.
  6. Deploy a config change, reload PHP-FPM, and repeat.

Load-test with realistic concurrent sessions if you expect traffic spikes during Dashain sales or booking peaks on travel platforms. Session driver latency under load matters as much as correctness.

Security hardening

Redis should not be publicly accessible. Bind to private network interfaces, require authentication, and restrict port 6379 with UFW. Session data contains user identifiers and sometimes sensitive flash content. Treat your Redis instance with the same care as your database. Broader server hardening practices are covered in Ubuntu web server hardening and securing websites in Nepal.

For teams without in-house DevOps capacity, pairing correct session architecture with managed Redis (AWS ElastiCache, DigitalOcean Managed Redis, or similar) reduces operational risk. Costs typically start around Rs 3,000–5,000/month (~USD 22–37) for small instances—cheap compared to lost checkout revenue on an eCommerce site.

Key Takeaways

  • Never use SESSION_DRIVER=file when more than one Laravel app server handles traffic—switch to Redis or database immediately.
  • Point all nodes at the same Redis 8.10 instance (or shared MySQL/PostgreSQL sessions table) with identical .env session and APP_KEY values.
  • Configure SESSION_DOMAIN, secure cookies, and trusted proxy headers so browsers send the session cookie correctly over HTTPS.
  • Reload PHP-FPM and rebuild config cache after session changes; stale opcache causes intermittent multi-server auth failures.
  • Test authentication by hopping between individual node URLs before enabling production load balancer traffic without sticky sessions.
  • Treat Redis network security and memory eviction policy as part of session reliability, not optional infrastructure detail.

People Also Ask

Can I use file sessions with NFS shared storage?

Technically yes, but it is a poor choice. NFS-backed file sessions introduce file-locking latency, permission headaches across nodes, and split-brain risk during network partitions. Redis or database sessions are simpler, faster, and better supported by Laravel's first-party tooling. NFS session storage is a legacy pattern you should avoid for new deployments.

Does Laravel Octane change session configuration for multi-server?

Octane keeps requests in long-lived workers, but it does not change the fundamental rule: session state must live in external storage all nodes share. Octane apps on multiple servers still need Redis or database sessions. Additionally, be careful with in-memory state leaking between requests in Swoole/RoadRunner workers—sessions themselves remain driver-dependent.

How do I migrate from file sessions to Redis without logging everyone out?

You cannot migrate existing file session files to Redis automatically in a meaningful way. Plan the switch during a low-traffic window. Deploy Redis configuration, change SESSION_DRIVER, clear config cache, reload PHP-FPM on all nodes simultaneously, and accept that active users will need to log in again once. Communicate a brief maintenance notice for admin users if needed.

Is database or Redis better for Laravel sessions in 2026?

Redis is faster for high-read session workloads and aligns with typical Laravel stacks that already use Redis for cache and queues. Database sessions are fine for moderate traffic, smaller teams, or when adding Redis is not yet justified. Both are production-viable for multi-server setups; Redis scales further before you need architectural changes.

Scale Laravel sessions with confidence

Correct Laravel session configuration for multi-server deployments comes down to one principle: session state belongs in shared storage, not on individual app disks. Configure Redis (or a database table), align cookies and proxy settings across every node, skip sticky sessions as a permanent fix, and validate by testing across nodes before you go live. The configuration itself takes minutes; getting every server, deploy script, and environment variable consistent is where experienced ops work pays off.

If you are scaling a Laravel application and want help designing the full stack—load balancers, Redis, zero-downtime deploys, and session-hardened authentication—review our Linux system administration services or ongoing Laravel support and maintenance. For greenfield projects, see web development services or read how we approach multi-tenant Laravel SaaS architecture. Need hands-on help auditing your current setup? Contact us and describe your server layout—we will pinpoint whether the issue is driver, cookie, or deploy drift.

Frequently Asked Questions

File sessions are stored per-node. The load balancer sends the next request to a different server that has never seen that session ID, so Laravel starts a fresh session.

Use redis or database. Redis is the default recommendation for most production Laravel clusters in 2026.

Install php8.5-redis on every Ubuntu app node and match REDIS_CLIENT across servers. Set SESSION_DRIVER=redis, point REDIS_HOST at one shared Redis 8.10 instance reachable over a private network—not localhost on each box. Use a dedicated REDIS_SESSION_DB index in config/database.php, keep SESSION_ENCRYPT, SESSION_SECURE_COOKIE, and APP_KEY identical on all nodes, then reload PHP-FPM after config:cache.

Sticky sessions mask file-session failures by routing users to the same node, but they are a bandage. They cause uneven load, painful deploys when a node drains, and full session loss when that node dies. Use shared Redis or database storage as the long-term fix; keep affinity only as a temporary migration crutch.

Laravel's default file driver writes session payloads to storage/framework/sessions on each server's local disk. Request one on Server A creates session abc123 locally; request two on Server B looks for abc123 on its own disk, finds nothing, and starts fresh—breaking auth, CSRF tokens, flash messages, and checkout flows.

If the app serves multiple subdomains like app.example.com and www.example.com, set SESSION_DOMAIN=.example.com so the laravel_session cookie works across hosts. Leave it null for single-host apps. Never set a domain that does not match the browser URL—browsers reject mismatched cookies silently, causing phantom logouts. Verify domain, Secure flag, and expiry in browser devtools.

Laravel encrypts session payloads when SESSION_ENCRYPT=true. Each node must share the same APP_KEY—Deployer 7 shared .env outside symlinked releases is the usual pattern. If one node gets a regenerated key during a bad deploy, it decrypts sessions differently and users appear logged out only on unlucky requests. Key rotation invalidates all sessions globally; plan that in a maintenance window.

Run php artisan session:table and migrate, then set SESSION_DRIVER=database on every node sharing the same MySQL 9.7 or PostgreSQL connection. Index the id column and schedule session:gc hourly for cleanup. Database sessions handle moderate traffic reliably on legal-tech portals until traffic justifies Redis, but high-traffic eCommerce should default to Redis from day one.

Yes, but session size is capped around four kilobytes and every request carries the full encrypted payload—not ideal for large admin sessions or heavy flash data.

Laravel must trust forwarded headers to detect HTTPS behind a reverse proxy. In Laravel 11+, configure trustProxies in bootstrap/app.php—restrict at: to your load balancer's private IP range in production rather than wildcard. Set SESSION_SECURE_COOKIE=true and APP_URL=https://yourdomain.com on every node, or Laravel may omit the Secure flag and browsers refuse cookies on HTTPS pages.

Configuration drift between nodes with different .env files is the top failure. Stale config:cache after SESSION_DRIVER changes causes intermittent auth bugs that look like random load-balancer behaviour. Forgetting to reload PHP-FPM, running Redis on localhost per node, aggressive Redis eviction under memory pressure, and mismatched SESSION_DOMAIN or APP_KEY are repeat offenders in production deployments.

API routes using Sanctum bearer tokens or Passport do not depend on shared session storage—token auth is stateless per request. Multi-server session configuration applies to browser-based session guards and Blade admin panels. SPA cookie authentication with Sanctum still needs shared sessions, and CSRF failures on AJAX subdomains usually mean SESSION_DOMAIN and Sanctum stateful domain lists are misaligned, not a bad @csrf directive.

Deploy two app nodes with shared Redis and identical .env. Log in via node A's direct IP, then hit node B with the same browser cookie via hosts file or Host header—confirm you stay authenticated and CSRF forms submit. Repeat through the load balancer with sticky sessions disabled. After config changes, reload PHP-FPM and retest. Load-test with realistic concurrent sessions before traffic spikes.

Using numeric DB indexes on one Redis 8.10 instance—e.g. REDIS_DB=0, REDIS_CACHE_DB=1, REDIS_SESSION_DB=2—lets you inspect laravel_session keys with redis-cli -n 2 without wading through cache entries. Define a dedicated session connection in config/database.php wired to config/session.php connection name. Larger deployments can run separate Redis instances entirely, but index separation simplifies debugging on moderate clusters.

Sessions are ephemeral, but aggressive Redis eviction under memory pressure can drop session keys mid-user-flow. Configure maxmemory-policy volatile-lru and size Redis appropriately for expected concurrent sessions. Monitor memory as part of routine Ubuntu server monitoring. Laravel aligns Redis TTL with SESSION_LIFETIME automatically on read, but eviction before TTL expiry still logs users out unexpectedly.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: