PHP Session Lifetime: An Adventure

We had a bit of a sticky situation here at the Centresource stomping grounds this past couple of weeks. We have a server with a multitude of environments served via our Apache webserver. It’s a fairly simple setup: we have a virtualhost devoted to development environments for all of our software developers, and then a plethora of virtualhosts for the various web-based applications we use: some home-brewed, some OSS web applications we use for various business functions (CMS, CRM, Groupware, etc..).

The mystery started when sessions started mysteriously expiring prematurely on two of our most popular web applications: DekkoTime, and our internal CRM/groupware application. It started about two weeks ago, with no discernable changes to our configuration that could be responsible.

So to understand what was necessary to track down this problem, we have to explore a little bit about how PHP session data storage and expiration works:

When PHP creates a session with session_start(), it dumps a file in a particular path. This is governed by the session.save_path parameter in php.ini — /tmp by default. But naturally, as sessions go idle or are abandoned, they need to be cleaned up, so that our save_path isn’t overwhelmed with old session data before it has a chanced to be cleaned (usually on reboot, in the case of /tmp). Enter garbage collection.

Garbage collection in PHP is, from what I gather, piggybacked on invocations of the PHP interpreter itself. When (if) it runs, it deletes any session files in the save_path that haven’t been accessed in a certain amount of time, governed by another php.ini setting: session.gc_maxlifetime. There are other parameters that dictate the probability/frequency with which the garbage collection routine runs, but they are irrelevant to this discussion.

So, naturally, the first thing I checked to see why our sessions were expiring was session.gc_maxlifetime in our php.ini:

session.gc_maxlifetime = 72000

72000 seconds — that’s 20 hours. So, no problem there. It appeared from our experience that sessions were expiring between 45 minutes to an hour — far less than 20 hours. I roughly verified the time that sessions were disappearing by initiating a new session and doing this:

date; while true; 
do 
if [ ! -f sess_235f09d44d5288554cf7a55fdfbc6df7 ]; 
then echo "session has disappeared" | mail cwage@centresource.com; 
break;
fi; 
sleep 1; 
done

That way, I’d get mailed when the session disappeared. Pretty sick, I know. This verified that sessions were disappearing after around 45 minutes of idle time. I could not find an explanation for this: session.gc_maxlifetime was set to 72000 in our php.ini. Maybe it was being overridden in that particular php environment? “print_r(ini_get(“session.gc_maxlifetime”))” bore the same result: 72000. No problem there.

Here I took a slight detour in wondering if there was something else diligently cleaning up an admittedly messy and full /tmp directory (~500 days of uptime will do that). So I started looking for some sort of utility that would let me monitor a file and see what process was responsible for unlinking it (the session file, that is). Sadly, there’s no utility that can accomplish this with a stock kernel in Linux: fwatch appears to accomplish this, but I wasn’t about to install a kernel module labelled as an alpha release just to track this down. Eventually I convinced myself, anyway, that the likelihood of some rogue process cleaning up /tmp was pretty unlikely, even for Linux.

So, I resorted to just googling my little heart out. Here’s where things get interesting.

Naturally, any application can override session.gc_maxlifetime to whatever pleases it — in fact, most OSS PHP applications do just this, in order to enforce its own particular idea of a sensible session expiration time. But here’s where things get sticky. If you override session.gc_maxlifetime in one particular environment, how does it know which sessions are its own, as opposed to others that should be adhering to the global setting?

Well, apparently, it doesn’t. When the PHP garbage collection routine runs, as far as I can tell, it blindly removes sessions from session.save_path that haven’t been accessed in longer than session.gc_maxlifetimeperiod. So, as it happens, what changed two weeks ago? We started playing with a number of PHP applications: Joomla! and Zen-Cart, both of whom (among others), take it upon themselves to override session.gc_maxlifetime to a smaller value, which appears to have been, drumroll please: around 45 minutes. So, every time the PHP interpreter was invoked in this environment, it obliterated sessions for all our other applications if they had been idle for 45 minutes or more. Harsh.

I am not sure what the preferred solution to this is supposed to be, and I’m also surprised that this isn’t a more common problem — overriding session.gc_maxlifetime is a fairly common thing for PHP applications to do these days. I am surprised these unexpected results would go unnoticed. In any event, my solution was just to create a hierarchy of per-application directories inside /tmp/php (owned by www-data, so Apache can write to them), and then adding a line to my Apache virtualhost config for each one, for example:

php_admin_value session.save_path /tmp/php4/dekko

In this way, the save_path is isolated for each application, so an overridden session.gc_maxlifetime for another codebase won’t affect it.

Phew.

Share This
  • Larry Kagan

    Some great info here. When I run into this problem, I will be silently thanking you for saving me the time. Have you contacted the authors of Joomla to see if they are aware of this affect and if they offer any additional solutions?

  • http://www.leftontheweb.com/ stefan

    We recently ran into a similar problem and I was actually planning on writing a small article on this. in our case, it was the other way around. Our application sets a longer maxlifetime than the one in php.ini, yet sessions timed out in a shorter period than what we set.

    I think the solution is easy: PHP should NEVER EVER allow you to change the maxlifetime without also changing the session.save_path ;)

    In an ideal world, PHP would tag each session with it’s own lifetime, so that the garbage collector will need to only delete those sessions whose lifetime tag has expired. But reality is that this doesn’t happen at the moment, and so session.save_path setting is essential when changing the maxlifetime.

  • http://blog.centresource.com/authors/chris Chris Wage

    No, but I may send them something. I think stefan below is right — if you change gc_maxlifetime, it should be considered a best-practice to change the save_path, too, lest you stomp over everything else. Beyond that, it would be nice if php itself enforced this..

  • http://www.abcphp.com Girish

    Isn’t it possible to set the session.save_path using .htaccess or ini_set? You see not everybody can edit the httpd.conf file ;-)

    Girish R
    http://www.abcphp.com

  • Ian

    I had the same problem with my sessions disappearing after about 45 minutes. My application was on a shared server so I guess someone had reduced maxlifetime. Anyway I solved it by using ini_set(‘session.save_path’,'mytmp’)
    Where mytmp is a directory on my site – of course I had to make sure the web server could write to mytmp directory. I guess it is a security risk in that the webserver can read the session files in mytmp – but that is another problem :-)

  • http://tmnloans.com Jim Russell

    I had exactly the same thing happen at Webmasters, a hosting company in Tampa, FL. They refused to believe it was anything at their end and that it must be my programming. I enclosed your article and they are now investigating. Compliments on a well researched and even better written article. Do you know of any shared hosting providers that have this matter sorted out? I need to find one for my commercial sites.

  • http://tmnloans.com Jim Russell

    Re: Ian’s suggestion about using the ini_set command in your code, what would be the actual values you would put in for the two names used in the example on an Apache/PHP server? For example, I am on a shared host and all of my files are stored in httpdocs. If this is an impossible question to answer, what questions do I need to ask my host in order to know how to construct that line of code for my environment?

  • http://blog.centresource.com/author/chris/ Chris

    Jim,

    If you do it in the code, you would probably want to take the following steps:

    1) Choose a path where you want the webserver to save its session data. This path needs to be writeable by the webserver, which can be accomplished in one of two ways:
    a) Make a directory in /tmp/ (i.e. /tmp/mysessdata)
    b) Make a directory in htdocs/ itself, i.e. htdocs/mysessdata, and make sure it’s writeable by the group that the webserver runs as (i.e. “www-data” or “nobody” — this will vary depending on your server)
    2) In your php code, you want to have a line like this somewhere in the flow of includes *before* you ever call session_start():

    ini_set('session.save_path', '/path/to/my/htdocs/sessdata');
    

    You can test this out by just creating a sample PHP file with only two lines: the ini_set() above and session_start();. If it works, you’ll see a session file created in the directory you made..

    So, the main things you’d need to know from your hosting provider are:

    1) What group does the webserver run under?
    2) Does the PHP/apache configuration allow me to change session.save_path with ini_set()? (it probably does)

    I think that’s really all you should need to know..

  • http://www.ypu.org Thomas De Groote

    I had about the same problem today, but already am using proper session_path and other settings for the domain. One domain works fine, the other one (different DocumentRoot) doesn’t: sessions disappear all of a sudden. I still have to find the reason. If anyone has an idea?

  • http://mrbubu.blogspot.com/ Bruno Cassol

    I’ve made an alternative to the instable session.gc_maxlifetime. It’s a simple PHP script that will erase session and cookies if passed some determined amount of minutes. This way you can set diferent session life for each application running in a server with only simple PHP.
    Check it here PHP Session Lifetime: A work around session.gc_maxlifetime

  • Nino

    Hello,

    Thank God there is a page like this! This is actually the info I need for the same problem. And my other problem ,upon reading the article and the comments, is I am posting my application in a shared web server where configuration/settings of PHP/Apache cant be customized.

    If I issue ini_set(‘session.save_path’, ‘/my_htdocs_path/my_session_data’); in my code and there is another application that did not set its own session.save_path, will the session data of that other application be saved in the save path I set? If so, then if that other application sets gc_maxlifetime to invoke the garbage collection frequently than I wanted for my application, then it erases the session data that I purposely separated. Please enlighten me. How should this problem be solved effectively considering the constraints?

    Thank you.

  • mansoor

    could you please let me know how to redirect a user to a page when the session gets time out. i had a lot of search done but in vain.
    Regards.

  • http://joom.ru joomla boy

    Yes. I think that it’s a big problem of php. There are no API to control sessions. Only php.ini’s configs.

  • Clark Everetts

    I’ve found a good way to handle session expiration globally is with a cron job that monitors the session data store and removes a session when it ages past its last access time plus your desired max session lifetime, not relying upon the probabilistic route used by the session extension in PHP. This is the approach you’ll find described in Ilia Alshanetsky’s book “php|architect’s Guide to PHP Security”. A good read.

  • http://www.ranger81.de/ ranger81.de

    thank you so much for this post. you solved my problem :)

  • http://devtime.blogspot.com Carlos Mafla

    Thank you so much for this post. This cleared a lot of questions that I had. I think much more information in the php manuals is needed about sessions, all this behavior seems very aleatory to me.

  • http://parasitehosting.com Steve

    Hey guys, you definitely should not put your session files in a web viewable directory. This would give the world the ability to read all of your session files, especially easy if the DirectoryList directive is allowed in the apache config.

    Any decent web host will give you access via ftp to a directory just below the root of your website. Find the full path to that directory and create your session folder there. It will still have the permissions that your website has, except it will be outside the site where predators cannot read the sessions. I believe the save path value will take full paths, or maybe it’s even required?

    Anyway, hopefully the people on this post won’t have their sessions hacked and have what could potentially be sensative data browsed by all of the internet! It’s not likely that someone will guess what your session directory is, but it would still be smart to keep that directory outside the web root of your site!

    Just my two cents!

  • http://www.disccovered.com Jesse

    I was just wondering, if i set a custon session_save_path with a max lifetime. will the garbage cleaner come and get it?. or do i have to set that to?

  • Jim Porter

    you should check out lsof it will let you see what processes are accessing particular files. watch is a handy utility as well. php5 has session overloading so that you can implement your own session management(I have one using memory based mysql tables).

    Lastly php4 has reached the end of it’s life and will no longer be supported after the end of this year.

    Hope this comment helps you or someone reading!

    Jim

  • http://blog.rats.at/ rattus

    I agree. Though, for those who need it they could set the permissions of the directory so that it cannot be browsed by the user, or — better — the directory could be moved outside the document root (which should also be the case with the default directory).

    Thanks to Chris for the original article.

  • Pingback: Project 2061 Techlog » PHP Session Timeout

  • Sheldon

    How can I set a finite timeout on a PHP session? I want my users to be logged in for 60 minutes regardless of activity/inactivity. I’m toying around with maxlifetime etc, but every time a new page is visited, the session renews itself, thus never expiring. I am using server sessions only, with a defined save_path.

    I’m presuming it’s done using some kind of date comparison function.

    Many thanks.

  • http://omnisitebuilder.com Casey

    I thought this would fix my problem on our server. I’ve separated my problem applications into their own folders under phptmp, and now the session files are being written there, however, two of our sites are still timing out users after only a few minutes. Our gc.maxlifetime is set to 5400 (90 minutes), but the folder change is not working. Anybody have any other ideas here? I’ve gone through all of my code and raised the maxlifetime to 5400 anywhere I could to know avail. It just seems like garbage collection is finding my files no matter where I put them. Please help!

  • http://www.php-learn-it.com Learn PHP

    For security purpose i also to save sessions in database rather than using flat files. Thou you would delete the file when you delete your sessions, but the file will remains readable by any script or human.

    You could also use memcache lib for storing sessions and cookies.

  • Boban Jovanoski

    I’ve found this type of solution for the same problem…but it only works fine on Mozilla….I’ve set the max session time lets say 20 seconds..used alternate session_path /tmp/something..permissions for something chmod a+rw……the user logs is and after 20 secs…has to login again (if he/she stays still on the web page) and it works fine on MOZILLA…but when i use Internet Explorer……the cookie doesent expire….i c the expiration time ends for 1 more day…..confusing$$!!!#$ sry for my bad english…but if some1 knows the solution for this thing pls e-mail me at bobanjovanoski@yahoo.com tnx in advance.
    P.S. Using sles9 for apache…and remote suse 10 for informix database….it all works sweett on mozilla…pls help me how to solve the session expire time on internet explorer too.

  • Kristjan Adojaan

    Thank you!! We have had exactly the same problems, but didn’t found an answer before read your Adventure. Sounds very logical!!! Now we try to implement.

  • http://tunes4u.org johnd

    Great information there thanks for that
    Our session.gc_maxlifetime is set to 1440 (php default) which i believe is 24 mins.
    Upon testing this i cant understand why after this time the session is still open
    im guessing this is due to the gc again as
    session.gc_probability = 1
    session.gc_divisor = 100
    so does that mean there is 1/100 chances that it will check for expired sessions
    and remove them
    its all very strange :0)

  • http://www.theatons.com Tom Taylor

    What an excellent article.
    I loved the way it was written, the approach and the detailed process.

    Bookmarked !

  • http://www.sumuladosnaweb.com.br Alessandro Nunes

    We had this problem in a dedicated server. So, no other users sharing or modifing gc_ variables. We use a single application, a CMS developed by us. But… we had a phpMyAdmin, intalled by the host company. We had no access to php config of phpMyAdmin so we ask the Host to verify: phpMyAdmin had only 24 minutes from session. They chaged this config, and the problem is solved. Thanks for the article!

  • John

    This solved all my problems on Ubuntu 9.1 – bottom line, you need to override PHP’s session.gc_maxlifetime value:

    /etc/phpmyadmin/config.inc.php

  • John

    that last post stripped out the actual meant of the solution, here it is again…hopefully:

    This solved all my problems on Ubuntu 9.1 – bottom line, you need to override PHP’s session.gc_maxlifetime value:

    include the following code in /etc/phpmyadmin/config.inc.php

    $cfg['LoginCookieValidity'] = 60*60*24*7*52;
    ini_set(‘session.gc_maxlifetime’, $cfg['LoginCookieValidity']);

  • http://www.k9stud.com Dogs For Sale

    This site provide some of very much important information. I must wanna thank the site owner for providing the fantastic site.

  • Manishlincon

    Thanks for your extended description and helped me quite a lot to make me understand how sessions work on clients machine. 

  • Manishlincon

    Thanks for your extended description and helped me quite a lot to make me understand how sessions work on clients machine. 

  • http://twitter.com/_sequoia Sequoia M.

    Bravo!