Stefan's weblog - PHP protip: Preventing those pesky session timeouts
Having problems with PHP sessions timing out on your web application or forms at inconvenient times? I'm going to tell you why that happens and show you how to fix it.
If you do enough serious work on PHP web applications like myself, you must have run into this problem. You wrote this awesome web application, CMS or just simply an advanced form. I mean anything that may take a few hours to fill in, especially including coffee breaks.
The reason I'm writing this is because the PHP documentation is simply not clear or helpful on this matter. Keep reading for more details.
Don't care about the technical details too much? Feel free to scroll down and skip to the solution.
When and why do PHP sessions expire?
Actually the real question is: Why do sessions stop working?
Sessions can stop working for two reasons:
- The cookie for the session in the user's web browser is gone, so the server has no way of attaching the user to a session anymore;
- The session information for the cookie has been deleted by the server
Making browsers remember session cookies a little longer is easy enough to fix by setting the ini-variable session.cookie_lifetime (example will be below). Now we know the browser is supposed to remember being logged on a little longer.
Next we need to prevent the server from forgetting about the session, which is a far more difficult task! It would seem easy enough... just set the session lifetime a bit longer, right? Wrong! There are two real problems here:
The first problem is the easiest to fix. PHP makes sure to garbage collect your sessions after a set timeout. This timeout is defined in php.ini with the identifier session.gc_maxlifetime, which was set to 1440 on my server. This value is in seconds, so that comes down to 24 minutes! That means no lunch breaks. No problem though, you can change that value in your own PHP script.
The second problem is far worse. PHP makes sure to garbage collect ALL your sessions after a set timeout. That means if you have multiple websites and/or multiple scripts that use sessions, any script will delete expired sessions for any website! Well, unless your virtual hosting provider is decent and have the scripts for each customer run under their own user, but many aren't i'm afraid. So different sites can clean up each other's sessions, but also different scripts on the same domain name. Have your own script plus a feedback form script you downloaded? That feedback form may delete your sessions too.
What this means is that you can set your garbage collect timeout to five weeks, but another script that has its timeout set to five minutes will delete your session after those five minutes without a second look.
You can prevent other web applications from deleting your sessions, of course. An easy way is running only your web application on the server and not mixing scripts on the same website. If your virtual hosting provider has you run PHP scripts as your own user, that at least prevents other customers from deleting (or any other kind of meddling with) your sessions.
A more rigid way of preventing this, and this is probably best in shared hosting environments, is using your own private directory for your sessions. This is relatively easy to set up, and other sites will only delete sessions from the shared session directory.
How to set this up
The first step is to make your own private session directory. Make sure this is writable by your PHP script. My example site would be www.mensink.nl located in /data/web/www.mensink.nl/ so i'd make the directory 'sessions'. My server runs all my scripts on my username, so I don't need to chmod anything here, but you might have to.
Make sure that visitors to your website won't be able to view your saved sessions by creating a .htaccess file in that sessions directory
echo "Deny from all" > /data/web/www.mensink.nl/sessions/.htaccess
Now on top of EVERY PHP-script you should add something like this above the session_start() (change the directory to your own directory):
ini_set('session.cookie_lifetime', 0); // keep cookies until the user closes the browser
ini_set("session.gc_maxlifetime", 60*60*24); // clean up sessions only after 24 hours
session_save_path('/data/web/www.mensink.nl/sessions/'); // use my private session directory
And that should do it. Your cookie lives until the user closes the browser (officially, but on my browser even past that), and sessions are kept on the server for 24 hours.
Of course, I say 'EVERY' PHP script. What I mean is every PHP script that is called and uses sessions. Don't bother with included files. Simply apply it to all the scripts where a session_start() is called and you should be fine.
That's it. Hope that was helpful to someone.