Users keep getting logged out of your PHP site? Check Debian's session cleanup

Your app asks for long sessions, but users are logged out after about half an hour without doing anything. On Ubuntu and Debian, PHP doesn't clean up old sessions itself. A separate job does, and it doesn't read your code. This guide shows how it works, how to fix it, and the opposite problem: session files that are never cleaned.

We tested every case in this guide on Debian 13 with Nginx and PHP 8.4. We made sessions in four different ways, ran Debian's cleanup job, and checked who was still logged in. Ubuntu uses the same packages and the same job.

Does this affect you?

Only if your app uses PHP's own sessions (session_start()) with the default file storage. Many custom PHP apps and older CMSs do. Laravel keeps sessions in its own storage (the database in new apps, or storage/framework/sessions) and cleans them itself, and WordPress doesn't use PHP sessions for logins, so neither is affected, unless a plugin or your own code calls session_start().

How Debian cleans up sessions

On Debian and Ubuntu, PHP's own cleanup is switched off (session.gc_probability = 0). Instead, a job called phpsessionclean runs every 30 minutes, at 9 and 39 minutes past the hour. Check that it's there:

systemctl list-timers phpsessionclean.timer

It deletes session files in /var/lib/php/sessions that haven't been used for longer than session.gc_maxlifetime. That's 1440 seconds, or 24 minutes, by default. The catch is where it reads that value: only from the php.ini files and their conf.d folders, and it takes the largest value it finds there.

The trap: settings in your code or pool are ignored

In our test, the ini files said 1 minute, so the test didn't take all day. Then we made sessions in different ways, waited, and ran the cleanup:

  • The app set a 1-day lifetime in its code with ini_set('session.gc_maxlifetime', 86400). PHP reported 86400. But the cleanup job deleted the session after 1 minute, and the user was logged out.
  • The site's PHP-FPM pool set 1 day with php_admin_value[session.gc_maxlifetime] = 86400. Same result: logged out.
  • The 1-day value was in PHP-FPM's conf.d folder. The cleanup job read it, waited 1440 minutes, and the user stayed logged in.

So with default settings, every session is deleted 24 minutes after the user's last click, whatever your app asks for.

The fix: set the lifetime where the job can see it

echo "session.gc_maxlifetime = 86400" | sudo tee /etc/php/8.4/fpm/conf.d/99-session-lifetime.ini
sudo systemctl reload php8.4-fpm

Change 86400 (one day) to what you need. To check what the cleanup job will use, run it in trace mode and look at the find line:

sudo bash -x /usr/lib/php/sessionclean 2>&1 | grep "find -O3"

In our test, it showed -cmin +1440 after the change: files are deleted after 1440 minutes without use.

This applies to every site that shares /var/lib/php/sessions. If only one site needs long sessions, give it its own folder (next section) and its own cleanup.

Also check session.cookie_lifetime. It's 0 by default, which means the browser forgets the session when it closes. If users should stay logged in after closing the browser, the app needs a "remember me" cookie or a longer cookie_lifetime. That's a separate setting from the cleanup.

The opposite problem: a folder that's never cleaned

Giving each site its own session folder is a good idea, because sites on the same server then can't read each other's sessions. But in our test, a pool with

php_admin_value[session.save_path] = /var/lib/php/sessions-shop

was never cleaned. The cleanup job only knows the folders named in the ini files, not those in pool files. The session file stayed after the cleanup ran, and on a busy site these files pile up until the disk or the folder is full.

Add a cleanup for each such folder. Put this in /etc/cron.d/session-cleanup-shop (no dots in the name, see why cron jobs don't run):

39 * * * * root find /var/lib/php/sessions-shop -name 'sess_*' -type f -cmin +1440 -delete

It's the same find command Debian's job uses. Change the folder and the number of minutes. Make sure the folder belongs to the pool's user and that others can't list it (chmod 1733, like Debian's own folder). See a PHP-FPM pool for each site.

Other reasons users get logged out

  • Sessions in a database or Redis: then this job doesn't apply. Check the app's own settings, and for Redis, its memory limit and eviction setting (see securing Redis).
  • The site moved between HTTP and HTTPS, or between www and non-www: the browser keeps a separate cookie for each, so the user looks logged out. See fixing redirects.
  • Several servers behind a load balancer: each server has its own session files. Use shared session storage (a database or Redis).

Quick list

  • Logged out after about 24 minutes: Debian's phpsessionclean uses gc_maxlifetime from the ini files only.
  • Fix: set session.gc_maxlifetime in /etc/php/8.4/fpm/conf.d/, not in code or the pool.
  • Check: bash -x /usr/lib/php/sessionclean | grep "find -O3".
  • Own session folder per site: add your own cleanup, or it's never cleaned.
  • Laravel and WordPress logins: not affected.

Official sources

Next steps: give each site its own PHP-FPM pool, and see where all the logs are when something else goes wrong.

ServerNest is being built to set up each site's PHP settings, including sessions, so they work the way your app expects. Join early access to try it first.

← Back to the security blog