By default, PHP stores sessions as files on the local disk (for example /var/lib/php/sessions). That works on a single server, but breaks when you run several app servers behind a load balancer: a user logged in on server A is logged out when the next request hits server B. Storing sessions in Redis solves that, and also avoids disk I/O and the session-file cleanup problems on busy sites.

Install Redis and the PHP extension

sudo apt install -y redis-server php8.3-redis
sudo systemctl enable --now redis-server
php -m | grep redis

See install Redis. For multiple app servers, run Redis on a central server (reachable over a private network or VPN), not on each app server.

Configure PHP to use Redis

Create /etc/php/8.3/fpm/conf.d/99-sessions.ini:

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?database=2&prefix=sess_&timeout=2"
session.gc_maxlifetime = 7200

With Redis authentication (recommended):

session.save_path = "tcp://10.0.0.5:6379?auth=your-redis-password&database=2"
sudo systemctl reload php8.3-fpm

Redis expires session keys automatically, using session.gc_maxlifetime as the TTL, so there’s no file garbage collection to worry about.

Verify

redis-cli -n 2 --scan --pattern 'sess_*' | head
redis-cli -n 2 TTL "sess_<id>"

Log in to your app, and you should see session keys appear. See redis-cli commands.

Session locking

PHP’s file handler locks a session for the duration of a request, so parallel AJAX requests from the same user run one at a time. phpredis supports locking too (redis.session.locking_enabled = 1), which prevents race conditions where two requests overwrite each other’s session changes. Enable it if your app writes to the session from parallel requests, and tune redis.session.lock_wait_time. Calling session_write_close() as soon as you’re done writing improves concurrency either way.

Frameworks

  • Laravel: set SESSION_DRIVER=redis in .env (and configure REDIS_*). Laravel handles sessions itself, independent of php.ini.
  • Symfony: use the RedisSessionHandler service.
  • WordPress core doesn’t use PHP sessions, but some plugins (e-commerce and membership plugins) do. php.ini settings apply to them. WooCommerce uses its own database-backed sessions.

Persistence and memory

  • Sessions are small, but many users add up. Set maxmemory, and use a policy that doesn’t evict sessions you need (volatile-lru evicts only keys with a TTL, which sessions have). See Redis maxmemory and eviction.
  • If users must stay logged in across Redis restarts, enable persistence (RDB or AOF). See Redis persistence.
  • Use a separate Redis database or instance for sessions, not the same one as a volatile cache that gets flushed.

Security

  • Don’t expose Redis to the internet: bind to localhost or a private interface, and set a password (requirepass, or ACL users). See install Redis.
  • Keep secure session cookie settings:
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = Lax
session.use_strict_mode = 1

Alternatives

  • Memcached (session.save_handler = memcached): also fast, but no persistence. See Redis vs Memcached.
  • Database sessions: simpler infrastructure, but slower.
  • Sticky sessions at the load balancer: avoids shared storage, but users lose sessions when a server goes down.

Quick reference

Setting Value
Extension php8.3-redis
Handler session.save_handler = redis
Path tcp://host:6379?database=2&auth=…
Lifetime session.gc_maxlifetime
Laravel SESSION_DRIVER=redis
Check redis-cli --scan --pattern 'sess_*'

Related: PHP-FPM tuning and Nginx load balancing.

  • PHP
  • Redis
  • Performance