How to automatically clean PHP session files left in the tmp directories of web accounts in an ISPConfig server environment

botond published March 2021, 03, Thu - 04:11 time

Content

 

Introductory

A PHP sessions are global variables that transmit data stored during web visits across multiple pages, i.e., are not lost when moving from one page to another. This allows, for example, that if someone logs in to a website with their username and password, this state will remain during their browsing until they log out or close the browser (unlike cookies), sessions end by default. when you close the browser). PHP accomplishes this by assigning unique IDs to visits (sessions) and creating files in the server-side file structure associated with those IDs in which it stores the data required for the session. The system files these files to a specific - usually tmp - placed in its directory, which is normally deleted after a specified time - by the garbage collector.

However, the situation is sometimes not so obvious. This is because if the PHP environment on the server changes, the garbage collector may not be able to delete these obsolete, redundant session files. In such cases, we need to make sure that these files are deleted regularly, otherwise a lot of them can accumulate over time, which also takes up storage space and can degrade system performance. This situation can usually occur when a custom session directory is set for a PHP environment so that the default cleanup script cannot find that directory. This is exactly the case with the ISPConfig server configuration, where due to the client file structure, each web account uses a different session directory.

Of course, this phenomenon can also occur with other server configurations, where we use custom PHP settings, but here only the ISPConfigWe review the solutions to be implemented on servers. So in this description, we will look at how to remedy this situation so that the session files are always deleted after the appropriate time has elapsed.

 

 

Detecting the problem

Not all websites use sessions, so we may not be affected by this problem, but we may not be aware of all of this, while many thousands or many tens of thousands of session files may have accumulated in one of the tmp directories on our ISPConfigos server. Therefore, let’s first look at whether this problem exists in our own repositories.

Scan tmp directories for web accounts

ISPConfig stores the tmp directories of web accounts in the following client directory structure:

/ var / www / clientX / webY / tmp /

Where X is the client ID and Y is the web account ID. If you did not create any clients in the control panel, "client0" will be the only client directory.

PHP session files are files beginning with "sess_" in these directories that end with an alphanumeric code, such as:

sess_s04fbu9mn8i8pljh30fr5l0he0

Manual inspection

As described above, look in the tmp directories of our web accounts. For example, in Web Account 1 under Client 1 (assuming it is):

ls -al /var/www/client1/web1/tmp

And we can see what's going on. If you can't find such files here, you don't have to do anything, go to the tmp directory of the next web account and look around there as well.

If we do not know what client directories we have, then a tree command to display the directory of all web accounts in a tree structure:

tree -d -L 2 /var/www/clients/

I have the output on the virtual server I have at hand:

Discovery of client directory structure in ISPConfigos server environment with tree command

There is currently only one clientless web account on this server, but it is clear how you list the structure. Accordingly, we can scroll through all the storage locations and view the tmp directories.

If you find that these files are present in large numbers in one of the tmp directories, you can also view them in chronological order:

ls -alt /var/www/client1/web1/tmp

Here is the -t switch puts the oldest files at the bottom so you can see how long your session file has been accumulating. If we want to count them, let’s add one wc command:

ls -alt /var/www/client1/web1/tmp | wc -l
The lifetime of these files is normally php.ini file is controlled by the "session.gc_maxlifetime" setting, which is specified in seconds. This variable is set to 1440 "factory" by default. 1440 seconds is 24 minutes, so these files must be deleted after a maximum of 24 minutes, unless this parameter is set to another value. If you see file (s) older than this value, the garbage collector will not work. In this case, depending on the traffic on the website, a large amount of session files may accumulate, which in the long run not only takes up a lot of space, but also slows down the handling of PHP sessions - thus degrading the overall performance of PHP.

 

 

Automated inspection

If you have a lot of clients and / or web accounts on your server, it is hard enough to check them one by one. For this purpose, you might want to put together a small shell script that walks through these tmp directories instead of us and counts the files beginning with "sess_" in them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#! /bin/bash
# ISPConfig fiókok tmp könyvtárainak vizsgálata
CLIENT_DIRS="/var/www/clients"                                                          # Kiindulási könyvtár: a kliens fájlstruktúra főkönyvtára
 
# Fő ciklus: kliens könyvtárak
find $CLIENT_DIRS -maxdepth 1 -type d -name "client[0-9]*" |                            # A find megkeresi a clientX könyvtárakat, majd csővezetéken át
    while read client_dir; do                                                           # ciklust készít a kapott eredményhalmazból
        echo "Kliens: $(basename $client_dir)"                                          # kiiratás
        WEB_DIRS=$client_dir                                                            # A web könyvtárakat beállítjuk az aktuális kliens könyvtárra.
                                                                                        # csak az áttekinthetőség miatt raktam ide
        # Belső ciklus: web könyvtárak
        find $WEB_DIRS -maxdepth 1 -type d -name "web[0-9]*" |                          # A find megkeresi a webY könyvtárakat, majd csővezetéken át
            while read web_dir; do                                                      # ciklust készít a kapott eredményhalmazból
                echo "Web: $(basename $web_dir)"                                        # kiiratás
                tmp_dir=$(realpath $web_dir/tmp)                                        # A tmp könyvtárat a teljes útvonallal állítjuk össze, 
                                                                                        # ezt a realpath paranccsal nyerjük ki a relatív útvonalból, amit a find-től kaptunk.
                echo "tmp könyvtár: $tmp_dir"                                         # kiiratás
                session_files=$(find $tmp_dir -type f -name 'sess_*' | wc -l)           # munkamenet fájlok megkeresése, majd a kapott lista elemek megszámolása
                echo "PHP munkamenet fájlok száma: "$session_files                        # darabszám kiiratás
                echo                                                                    # üres sor a webkönyvtárak között
            done
        echo                                                                            # plusz üres sor a kliensek között, hogy áttekinthetőbb legyen a kimenet, ha több kliens van.
    done

Save it to a file, give it run permission. For example:

chmod +x scan_tmp

Then let's run. For me, the output of this program on the server is, for example:

Automated checking of PHP session files in tmp directories

So this program does nothing but make a find command and cycles through the client directory structure and prints the number of PHP session files found.

For example, I have 3 websites that use PHP sessions, and currently these session files are within normal limits - the amount of which varies, of course, depending on the time of day and traffic. In the past, session files were collected properly on these as well, until I created my own cleaning program for it.

If we run this program and find values ​​of 0, or see only low numbers like here, we have nothing more to do. There are websites that take care of cleaning the session files themselves, so there is no need for external intervention in such cases. However, if you find an unrealistically large amount of PHP session files in one of our web accounts, let’s move on to the cause of the problem first.

 

Exploring the cause of the problem

In order to remedy the problem, we must first know the cause as well.

Cron system examination

The cron system provides for the execution of scheduled tasks on UNIX-like operating systems, so we need to look around here, as cleaning up session files is also a scheduled task. To do this, go to /etc/cron.d directory and then list:

cd /etc/cron.d
ls -al

Here you can see several files, such as the components of the ISPConfig server environment, and among them you can see a php file, let's look at this:

cat php

View the php cron file

Here we can see a cron entry, which is described in the comment below:

#  This purges session files in session.save_path older than X,
#  where X is defined in seconds as the largest value of
#  session.gc_maxlifetime from all your SAPI php.ini files
#  or 24 minutes if not defined.  The script triggers only
#  when session.save_handler=files.
#
#  WARNING: The scripts tries hard to honour all relevant
#  session PHP options, but if you do something unusual
#  you have to disable this script and take care of your
#  sessions yourself.

# Look for and purge old sessions every 30 minutes
08,38 *     * * *     root   [ -x /usr/lib/php/sessionclean ] && if [ ! -d /run/systemd/system ]; then /usr/lib/php/sessionclean; fi

The bottom line is that this cron task deletes the session files that the php.ini are in the directory specified in the "session.save_path" setting and are older than the seconds specified in the "session.gc_maxlifetime" setting, which is set to the 24-minute time threshold by default.

It also warns you that this script will run the relevant php.ini settings, so if you make any custom changes to your system, you'll need to disable this script and fix the sessions yourself.

The cron job queue runs as root every 09 and 39 minutes after preliminary checks (whether the file exists and can be run, the systemd service is not running). / usr / lib / php / sessionclean file, which is PHP's default session peeling program.

 

 

Also, in addition to cron, there is even one in systemd phpsessionclean service, which also runs the same program at the same times. You can also check this with the following command:

cat /var/log/syslog | grep "php session" | tail -30

For example, I see that this service runs at exactly predefined times:

Check the phpsessionclean Systemd service

So the above program is also guaranteed to run from two locations. For example, if the service does not work, cron will run the program. Then what could be the problem?

There may be a problem with the program on an exclusionary basis, so we need to look into this further.

Scan the default php sessionclean program

/ usr / lib / php / sessionclean program is a shell script, let's look at it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/bin/sh -e
#
# sessionclean - a script to cleanup stale PHP sessions
#
# Copyright 2013-2015 Ondřej Surý <ondrej@sury.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
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

Simply put, the program does the query of the PHP versions available in our system, then goes through these PHP versions in the main loop, and goes through the server APIs (SAPI) in an internal loop, thus traversing all the server APIs of all PHP versions. (apache, CGI, FPM, CLI) and read the required SESSION settings, such as "save_path" and "gc_maxlifetime", from each of the appropriate php.ini files. Then deletes files older than "sess_" older than gc_maxlifetime seconds from the save_path path. So it does exactly what was mentioned above in the cron task comment.

For example, this cleaning program is a LAMP server also works properly where php.ini is the default / var / lib / php / sessions is used to store sessions, but in the ISPConfig control panel if a web page is PHP-FPM server API, you put the web page in a separate FPM pool, which would still be a problem, just set the above-mentioned save_path value in the configuration file of the web pool - the tmp directory of the web account - not in the php.ini file. And the above cleanup script does not cover the configuration files of PHP-FPM pools, but only reads the session.save_path and gc_maxlifetime values ​​from the "factory" php.ini files to perform the deletions. Thus, this program does not delete the PHP session files of the websites run by PHP-FPM SAPI from the paths individually configured in the ISPConfig server configuration, so we have to take care of this separately.

 

The solutions

I also present two variations on the above problem, these are my own recipes that have already worked for me. The following examples only work in an ISPConfig server environment!

Delete PHP session files per web page

The following script runs in the cron of a specific web page to delete PHP session files stored in the tmp directory of the web account:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# PHP munkamenet pucoló script
# A PHP csak a gyári beállítású munkamenet könyvtárból törli a munkameneteket.
# Így ha attól eltérő a beállítás, akkor nem pucolja őket.
# Ezáltal rengeteg fájl halmozódik fel a munkamenet könyvtárban
 
# Változók:
DOMAIN="domain.tld"                                                     # Domain nevet itt kell beállítani        
SESSION_MAXLIFETIME=3600                                                # Munkamenet élettartam másodpercekben (egyéni beállítás)
 
 
SESSION_PATH="/var/www/$DOMAIN/tmp"                                     # Webfiók tmp könyvtára az ISPConfig-os környezetben
MINUTE=$((SESSION_MAXLIFETIME / 60))                                    # kiszámítjuk a perceket a find parancs számára
 
find $SESSION_PATH -type f -name 'sess_*' -cmin "+$MINUTE" -delete      # elévült munkamenet fájlok törlése

So if you want to delete session files individually for a particular webpage because, for example, you use a different expiration time, set the domain in this script, then the expiration time in seconds, then save it somewhere in the file structure of the web account document and set it in ISPConfig. to a web account as a separate cron task, running every 10 minutes.

I usually use the private / bin directory below the document root for such separate script files, so my own programs don't mix with other things.

For cron settings, set the command to run as follows:

/ var / www /domain.tld/ private / bin /script name

Where domain.tld is the domain of the web account and the script name is what we saved the script above.

This allows you to set up PHP session deletion for a specific web page with a custom timeout setting.

 

 

Delete PHP session files globally

The other variation is to delete the obsolete PHP session file for all web accounts from a single location. The advantage of this is that it only needs to be created in one place and configured once in cron as well. The disadvantage is that the time limit set here will apply to all web accounts, which of course is irrelevant in the vast majority of cases, so it has more advantages over the previous version. The program therefore:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#! /bin/bash
# ISPConfig fiókok tmp könyvtárainak vizsgálata
SESSION_MAXLIFETIME=3600                                                                # Munkamenet élettartam másodpercekben (globális beállítás)
CLIENT_DIRS="/var/www/clients"                                                          # Kiindulási könyvtár: a kliens fájlstruktúra főkönyvtára
 
MINUTE=$((SESSION_MAXLIFETIME / 60))                                                    # kiszámítjuk a perceket a find parancs számára
 
 
echo "SESSION_MAXLIFETIME értéke: "$SESSION_MAXLIFETIME                                   # Kiiratás
 
 
# Fő ciklus: kliens könyvtárak
find $CLIENT_DIRS -maxdepth 1 -type d -name "client[0-9]*" |                            # A find megkeresi a clientX könyvtárakat, majd
    while read client_dir; do                                                           # ciklust készít a kapott eredményhalmazból
        echo "Kliens: $(basename $client_dir)"                                          # kiiratás
        WEB_DIRS=$client_dir                                                            # A web könyvtárakat beállítjuk az aktuális kliens könyvtárra.
                                                                                        # csak az áttekinthetőség miatt raktam ide
        # Belső ciklus: web könyvtárak
        find $WEB_DIRS -maxdepth 1 -type d -name "web[0-9]*" |                          # A find megkeresi a webY könyvtárakat, majd
            while read web_dir; do                                                      # ciklust készít a kapott eredményhalmazból
                echo "Web: $(basename $web_dir)"                                        # kiiratás
                tmp_dir=$(realpath $web_dir/tmp)                                        # A tmp könyvtárat a teljes útvonallal állítjuk össze, 
                                                                                        # ezt a realpath paranccsal nyerjük ki a relatív útvonalból, amit a find-től kaptunk.
                echo "tmp könyvtár: $tmp_dir"                                         # tmp könyvtár kiiratása
                
                session_files=$(find $tmp_dir -type f -name 'sess_*' | wc -l)           # munkamenet fájlok megkeresése, majd a kapott lista elemek megszámolása
                echo "PHP munkamenet fájlok száma a törlés előtt: "$session_files      # darabszám kiiratás a törlés előtt
                
                find $tmp_dir -type f -name 'sess_*' -cmin "+$MINUTE" -delete           # elévült munkamenet fájlok törlése                
                
                session_files=$(find $tmp_dir -type f -name 'sess_*' | wc -l)           # munkamenet fájlok megkeresése, majd a kapott lista elemek megszámolása
                echo "PHP munkamenet fájlok száma a törlés után: "$session_files       # darabszám kiiratás a törlés után
 
                echo                                                                    # üres sor a webkönyvtárak között
            done
        echo                                                                            # plusz üres sor a kliensek között, hogy áttekinthetőbb legyen a kimenet, ha több kliens van.
    done

Here, we use the double-cycle directory traversal used in the automated library verification section presented in the first part of the description as a framework in which to embed the deletion part of the previous version. Domain names no longer play here, so it has also been removed. All you have to do is set the time limit, which I usually set to 3600 seconds for each website. Also, this program does not scan any php.ini file, so in this we need to set the appropriate limit once.

This is even the "debug" version in which we dump everything. The result for me is:

PHP session delete script output

Since this deletion program runs by me every 10 minutes by default, the session files accumulated between runs every 10 minutes are deleted, which can be clearly seen in the numbers.

If we've tested it and everything works fine, we can even take the deletions or counts out of it, since we won't need them anymore when we run it in cron. Of course, you might want to do this version as well if you want to see the results at any time.

And the cleaned version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#! /bin/bash
# ISPConfig fiókok tmp könyvtárainak vizsgálata
SESSION_MAXLIFETIME=3600                                                                # Munkamenet élettartam másodpercekben (egyéni beállítás)
CLIENT_DIRS="/var/www/clients"                                                          # Kiindulási könyvtár: a kliens fájlstruktúra főkönyvtára
 
MINUTE=$((SESSION_MAXLIFETIME / 60))                                                    # kiszámítjuk a perceket a find parancs számára
 
# Fő ciklus: kliens könyvtárak
find $CLIENT_DIRS -maxdepth 1 -type d -name "client[0-9]*" |                            # A find megkeresi a clientX könyvtárakat, majd
    while read client_dir; do                                                           # ciklust készít a kapott eredményhalmazból
        WEB_DIRS=$client_dir                                                            # A web könyvtárakat beállítjuk az aktuális kliens könyvtárra.
                                                                                        # csak az áttekinthetőség miatt raktam ide
        # Belső ciklus: web könyvtárak
        find $WEB_DIRS -maxdepth 1 -type d -name "web[0-9]*" |                          # A find megkeresi a webY könyvtárakat, majd
            while read web_dir; do                                                      # ciklust készít a kapott eredményhalmazból
                tmp_dir=$(realpath $web_dir/tmp)                                        # A tmp könyvtárat a teljes útvonallal állítjuk össze, 
                                                                                        # ezt a realpath paranccsal nyerjük ki a relatív útvonalból, amit a find-től kaptunk.
                find $tmp_dir -type f -name 'sess_*' -cmin "+$MINUTE" -delete           # elévült munkamenet fájlok törlése                
            done
        echo                                                                            # plusz üres sor a kliensek között, hogy áttekinthetőbb legyen a kimenet, ha több kliens van.
    done

This can already go into cron after we have saved it and given it run privileges.

Since it is not directly related to any of the web hosts and you need root privileges to run it, let's set it up manually:

sudo nano /etc/cron.d/my_sessionclean

Then add the following line:

*/10    *    *    *    *    root    /scriptunk/eleresi/utja/my_sessionclean >/dev/null

Of course, we use our own path and script name.

 

 

Conclusion

With these methods, we can ensure that PHP session files never accumulate in the tmp directories of our web accounts created by ISPConfig. Be sure to leave the original sessionclean program because other than websites managed by ISPConfig. Apache PHP applications running with configurations, such as phpMyAdmin also, specified in the php.ini file / var / lib / php / sessions places the PHP session files in the directory that the original cleaner maintains.