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=redisin.env(and configureREDIS_*). Laravel handles sessions itself, independent of php.ini. - Symfony: use the
RedisSessionHandlerservice. - 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-lruevicts 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.