Session timeouts in PHP: best practices

Each time session_start is called the session files timestamp (if it exists) gets updated, which is used to calculated if session.gc_maxlifetime has been exceeded.

More importantly you can't depend on a session to expire after session.gc_maxlifetime time has been exceeded.

PHP runs garbage collection on expired sessions after the current session is loaded and by using session.gc_probability and session.gc_divisor it calculates the probability that garbage collection will run. By default its a 1% probability.

If you have a low number of visitors there is a probability that an inactive user could access a session that should have expired and been deleted. If this is important to you will need to store a timestamp in the session and calculate how log a user has been inactive.

This example replaces session_start and enforces a timeout:

function my_session_start($timeout = 1440) {
    ini_set('session.gc_maxlifetime', $timeout);
    session_start();

    if (isset($_SESSION['timeout_idle']) && $_SESSION['timeout_idle'] < time()) {
        session_destroy();
        session_start();
        session_regenerate_id();
        $_SESSION = array();
    }

    $_SESSION['timeout_idle'] = time() + $timeout;
}

I spent some time looking for a good answer to how the php.ini server settings make sessions expire. I found a lot of info but it took a while to figure out why the settings work the way they do. If you're like me, this might be helpful to you:

Sessions are stored as cookies (files on the client's pc) or server side as files on the server. Both methods have advantages and disadvantages.

For the sessions stored on the server, three variables are used.

session.gc_probability session.gc_divisor session.gc_maxlifetime

(session.gc_probability/session.gc_divisor) produces the probability that the garbage collection routine will run. When the garbage collector runs, it checks for session files that haven't been accessed for at least session.gc_maxlifetime and deletes them.

This is all explained pretty well in forum posts (this one especially!) - But the following questions do come up:

1.) How is that probability applied? When does the server roll the dice?

A: The server rolls the dice every time session_start() is called during any active session on the server. So this means you should see the garbage collector run roughly once for every 100 times that session_start() is called if you have the default of session.gc_probability = 1 and session.gc_divisor = 100

2.) What happens on low volume servers?

A: When session_start() is called it FIRST refreshes the session and makes the session values available to you. This updates the time on your session file on the server. It THEN rolls the dice and if it wins (1 out of 100 chance) it calls the garbage collector. The garbage collector then checks all session id files and sees if there are any that are eligible for deletion.

So this means that if you are the only person on the server, your session will never go inactive and it will appear as though changing the settings have no effect. Let's say you change session.gc_maxlifetime to 10 and session.gc_probability to 100. This means there is a 100% chance the garbage collector will run and it will clear out any session files that haven't been accessed in the last 10 seconds.

If you're the only one on the server, your session will not be deleted. You need at least 1 other active session running for yours to go inactive.

So basically, on a low volume server or at a low volume time - it could be MUCH longer than session.gc_maxlifetime before the garbage collector actually runs and the sessions are actually deleted. And without knowing how this works, it may appear completely random to you.

3.) Why do they use the probability?

A: Performance. On a higher volume server you don't want the garbage collector running on every request of session_start(). It will slow down the server needlessly. So depending on your server volume, you may want to increase or decrease the probability that the garbage collector runs.

I hope that this ties things together for you. If you're like me and you tried session.gc_maxlifetime and it didn't seem to work (because you tried it out on a development server so as not to disturb anyone), then this post hopefully saved you some head scratching.

Good luck!