0

Description and Setup

I have a problem where some PHP session files are being deleted prior to reaching their session.gc_maxlifetime threshold.

Operating System: Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-188-generic x86_64)

PHP Version: 7.2.24-0ubuntu0.18.04.12 and some more PHP (php.ini) configuration:

session.gc_divisor => 10 => 10
session.gc_maxlifetime => 43200 => 43200
session.gc_probability => 1 => 1
session.save_path => /var/lib/php/sessions => /var/lib/php/sessions
session.save_handler => files => files

So I expected to see all sessions older than 12h to be deleted by (I assume) either PHP's garbage collector or this cronjob from PHP (found in /etc/cron.d/php):

09,39 *     * * *     root   [ -x /usr/lib/php/sessionclean ] && if [ ! -d /run/systemd/system ]; then /usr/lib/php/sessionclean; fi

And deleting session files older than 12h works very well. It's just that some session files for some reason don't last 12h. Here the contents of the /usr/lib/php/sessionclean-script being executed by the cronjob (if it helps):

SAPIS="apache2:apache2 apache2filter:apache2 cgi:php@VERSION@ fpm:php-fpm@VERSION@ cli:php@VERSION@"

# Iterate through all web SAPIs
(
proc_names=""
for version in $(/usr/sbin/phpquery -V); do
    for sapi in ${SAPIS}; do
    conf_dir=${sapi%%:*}
    proc_name=${sapi##*:}
    if [ -e /etc/php/${version}/${conf_dir}/php.ini ]; then
        # Get all session variables once so we don't need to start PHP to get each config option
        session_config=$(PHP_INI_SCAN_DIR=/etc/php/${version}/${conf_dir}/conf.d/ php${version} -c /etc/php/${version}/${conf_dir}/php.ini -d "error_reporting='~E_ALL'" -r 'foreach(ini_get_all("session") as $k => $v) echo "$k=".$v["local_value"]."\n";')
        save_handler=$(echo "$session_config" | sed -ne 's/^session\.save_handler=\(.*\)$/\1/p')
        save_path=$(echo "$session_config" | sed -ne 's/^session\.save_path=\(.*;\)\?\(.*\)$/\2/p')
        gc_maxlifetime=$(($(echo "$session_config" | sed -ne 's/^session\.gc_maxlifetime=\(.*\)$/\1/p')/60))
        
        if [ "$save_handler" = "files" -a -d "$save_path" ]; then
        proc_names="$proc_names $(echo "$proc_name" | sed -e "s,@VERSION@,$version,")";
        printf "%s:%s\n" "$save_path" "$gc_maxlifetime"
        fi
    fi
    done
done
# first find all open session files and touch them (hope it's not massive amount of files)
for pid in $(pidof $proc_names); do
    find "/proc/$pid/fd" -ignore_readdir_race -lname "$save_path/sess_*" -exec touch -c {} \; 2>/dev/null
done ) | \
    sort -rn -t: -k2,2 | \
    sort -u -t: -k 1,1 | \
    while IFS=: read -r save_path gc_maxlifetime; do
    # find all files older then maxlifetime and delete them
    find -O3 "$save_path/" -ignore_readdir_race -depth -mindepth 1 -name 'sess_*' -type f -cmin "+$gc_maxlifetime" -delete
    done

exit 0

What I tried

To test how long a session file would persist, I just called/opened my website on a site with the following content and waited:

<?php

    session_start();
    
    var_dump( session_id() );
    phpinfo();

While waiting I would insert my sessionid into the following script, which checks every minute whether or not the session file has been deleted:

#! /bin/bash

file=/var/lib/php/sessions/sess_[sessionid]

while test -f "$file"; do
    now=$(date +"%T")
    echo "$now: $file still exists"
    sleep 60
done

now=$(date +"%T")
echo "$now: $file was deleted!"

I observed multiple sessions like this. From the sessions I observed, the session that lastet the longest, didn't even last for 5h. Which is odd, because in the session.save_path-folder (/var/lib/php/sessions) are >100 session files which seem to persist for atleast the full 12h.

I have already read the arcticles below, without any success:

I am stuck with this for 4 days now, any help would be much appreciated.

Alpaca
  • 11
  • 5
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Dec 03 '22 at 05:41
  • When they are deleted after 12h, then there's no problem. Sessions may expire earlier when not used. That is normal behavior. Why would you expect a session when not used? Having unused sessions in memory database like memcached or redis would be not greatful. – Markus Zeller Dec 03 '22 at 20:03
  • @Markus Zeller I would expect the session to still be there, because I have variables saved in $_SESSION which I need to access. And when do sessions count as 'expired' and can this behavior be changed? – Alpaca Dec 05 '22 at 07:59
  • I would say this comes from the session garbage collector. The default divisor should be 100, but yours is 10. So it is probably more cleaning than usual. https://www.php.net/manual/en/session.configuration.php#ini.session.gc-divisor – Markus Zeller Dec 05 '22 at 09:26
  • @AD7six What defines when a session is expired? No recurring http request in a amount of time? There's too less information to say that. Would be interesting to know if changing the GC frequency changes this behavior. – Markus Zeller Dec 05 '22 at 12:25
  • @Markus Zeller `What defines when a session is expired? No recurring http request in a amount of time?` I'll test that by setting `gc_maxlifetime` to 12h and writing the current time to `$_SESSION['keepSessionAlive']` every 20minutes via a JavaScript ajax-call while doing nothing else on that website. I'll just keep it open and observe. I've already noticed that writing to the superglobal variable `$_SESSION` updates the `Last modified`-date of the session file. Maybe that keeps it from being deleted despite being inactive on the website. I'll give feedback on that as soon as I can. – Alpaca Dec 05 '22 at 12:55
  • @MarkusZeller are you asking me to explain how file-based sessions work server-side or making a statement there? A session file is only deleted after the maxlifetime since it was last modified; any call to session_destroy will also delete the file on the server - those are the only two normal cirsumstances AFAIK. I missed that the gc script is mentioned in the question, it does [the same thing I mentioned previously](https://sources.debian.org/src/php-defaults/49/sessionclean/#L54-L55). – AD7six Dec 05 '22 at 14:22
  • @AD7six `A session file is only deleted after the maxlifetime since it was last modified` I think that's wrong. A few hours ago I've tested that by setting `gc_maxlifetime` back to 24minutes and writing the current time to `$_SESSION['keepSessionAlive']` every 10seconds via a Javascript ajax-call. Writing to that session file continuously updated the `Last modified`-date of that session file. The session file lastet 26-27minutes from the point of the creation of the file. Meaning, the 24minute-timer does not get reset with an `Last modified`-date-update. – Alpaca Dec 05 '22 at 14:44
  • @AD7six No, I am not asking you that. But you also don't have the final answer why his session files are deleted too early. So I just made a suggestion on what I would try to investigate. – Markus Zeller Dec 05 '22 at 15:25
  • `I think that's wrong` @Alpaca look at the source for the script that deletes session files - it is literally the only thing it does. If your session files are being deleted at other times it's not happening from the GC process (unless you investigate and provide direct evidence that's what's happening). As such the premise of the question is misleading and I think you're looking in the wrong place - you need to investigate and provide details (not observational anecdotes). – AD7six Dec 05 '22 at 16:31
  • @AD7six I see. It seems my observation regarding the `I think that's wrong` has some flaws anyway. The session file from my test, where I set `gc_maxlifetime` to 12h, still exists after 18,5h... . So, I'm lost again. My tests don't give consistent results. Maybe you're right and it's just the cronjob which deletes the session files all the time and I just haven't noticed. But then again, why hasn't the cronjob already deleted this 18,5h old session? What am I missing? – Alpaca Dec 06 '22 at 08:54

1 Answers1

0

After multiple weeks of trying to get it to work, I've given up on trying to achieve session file deletion with the php garbage collector or the php cronjob. I just can't get either of them to work consistently.

I've now solved the problem by..

  • disabling php's garbage collector (by setting gc_probability to 0)
  • disabling php's cronjob (by commenting-out the cronjob line with a #)
  • taking care of deleting all contents of the folder, which contains the session files, in regular intervals by a self-written cronjob

I've set the cronjob to run at 4am and 4pm and it's working like a charm.

Alpaca
  • 11
  • 5