PHP Sessions

Su Subhalakshmi Chandran Updated 17 Oct 2026
7 min read ·Lesson 65 of 248
In this post

The $_SESSION lesson introduced sessions: starting them, storing values, logging users in and out, and flash messages. This lesson looks at how sessions work internally and how to run them reliably in

The $_SESSION lesson introduced sessions: starting them, storing values, logging users in and out, and flash messages. This lesson looks at how sessions work internally and how to run them reliably in real applications: the session lifecycle, configuration, timeouts, locking, and storing sessions in a database or Redis.


The Session Lifecycle

Request arrives with cookie PHPSESSID=abc123
        │
        ▼
session_start()
   ├── open the storage
   ├── read the data for "abc123"   (and lock it)
   └── unserialize it into $_SESSION
        │
        ▼
Your script reads and modifies $_SESSION
        │
        ▼
End of script (or session_write_close())
   ├── serialize $_SESSION
   ├── write it back for "abc123"
   └── release the lock

Occasionally, session_start() also triggers garbage collection, which deletes sessions that have not been used for longer than session.gc_maxlifetime seconds.

Checking the Session Status

<?php
function ensureSession(): void
{
    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
}

var_dump(session_status() === PHP_SESSION_NONE);
ensureSession();
var_dump(session_status() === PHP_SESSION_ACTIVE);
ensureSession();   // safe to call again

Output

bool(true)
bool(true)
ConstantMeaning
PHP_SESSION_DISABLEDSessions are disabled on this server
PHP_SESSION_NONESessions are available, but none is started
PHP_SESSION_ACTIVEA session is active

Calling session_start() twice triggers a notice; ensureSession() avoids this in code that may run in different contexts.


Configuring Sessions

Session behavior is controlled by session.* settings, which can be set in php.ini or passed as an options array to session_start() (without the session. prefix):

<?php
session_start([
    'name' => '__Host-SID',
    'cookie_lifetime' => 0,
    'cookie_path' => '/',
    'cookie_secure' => true,
    'cookie_httponly' => true,
    'cookie_samesite' => 'Lax',
    'use_strict_mode' => true,
    'use_only_cookies' => true,
    'gc_maxlifetime' => 7200,
]);
SettingRecommendedPurpose
nameA custom name, e.g. __Host-SIDHides the default PHPSESSID and enables the __Host- cookie protections
cookie_lifetime0Cookie ends when the browser closes
cookie_secure / cookie_httponly / cookie_samesitetrue / true / LaxProtects the session cookie (see PHP Cookies)
use_strict_modetrueRejects session IDs that the server did not create
use_only_cookiestrueNever accepts session IDs from URLs
gc_maxlifetimeDepends on your app (e.g. 7200)Inactive sessions older than this may be deleted
sid_length, sid_bits_per_characterDefaultsDeprecated in PHP 8.4; leave them unset

Applying the same settings in php.ini (for example session.cookie_secure = 1) makes them the default for the whole server.


Session Timeouts

gc_maxlifetime is not a precise timeout: garbage collection runs only occasionally, and some systems handle it differently. Applications that need reliable timeouts store timestamps in the session and check them on every request.

Two kinds of timeout are common:

  • Idle timeout — log out after a period of inactivity (for example, 30 minutes).
  • Absolute timeout — log out after a maximum session age, even if the user is active (for example, 8 hours).
<?php
function checkSessionAge(array &$session, int $now, int $idleLimit, int $absoluteLimit): string
{
    $session['created_at'] ??= $now;
    $session['last_activity'] ??= $now;

    if ($now - $session['last_activity'] > $idleLimit) {
        return 'expired: idle';
    }

    if ($now - $session['created_at'] > $absoluteLimit) {
        return 'expired: too old';
    }

    $session['last_activity'] = $now;

    return 'valid';
}

$idle = 30 * 60;          // 30 minutes
$absolute = 8 * 3600;     // 8 hours

$session = ['created_at' => 10_000, 'last_activity' => 10_000];
echo checkSessionAge($session, 10_600, $idle, $absolute) . "\n";   // 10 minutes later
echo checkSessionAge($session, 13_000, $idle, $absolute) . "\n";   // 40 minutes after last activity

$longSession = ['created_at' => 10_000, 'last_activity' => 38_000];
echo checkSessionAge($longSession, 39_000, $idle, $absolute) . "\n";

Output

valid
expired: idle
expired: too old

How It Works

  1. On the first request, both timestamps are initialized.
  2. If more than 30 minutes passed since the last activity, the session has expired due to inactivity.
  3. If the session is older than 8 hours, it has expired regardless of activity.
  4. Otherwise, the activity timestamp is updated.

In a real application, checkSessionAge($_SESSION, time(), ...) runs at the start of every request; on expiry, the application destroys the session and redirects to the login page. The function receives the session array by reference and the current time as a parameter, so it can be tested with fixed numbers, as shown.

Regenerating the ID Periodically

Besides regenerating the session ID at login, regenerating it periodically (for example, every 15 minutes) limits how long a stolen ID remains useful:

<?php
if (($_SESSION['regenerated_at'] ?? 0) < time() - 900) {
    session_regenerate_id(true);
    $_SESSION['regenerated_at'] = time();
}

Session Locking and Concurrency

With the default file storage, session_start() locks the session until the script ends. Requests from the same user are therefore processed one after another:

Request A (slow report, 8 s) ──── holds the session lock ─────────►
Request B (simple AJAX call)      waits ......................... ► runs

Releasing the Lock Early

<?php
session_start();
$userId = $_SESSION['user_id'] ?? null;
session_write_close();   // save changes and release the lock

// long-running work that does not modify the session

Read-Only Sessions

<?php
session_start(['read_and_close' => true]);

echo $_SESSION['user_name'] ?? 'Guest';

read_and_close reads the session data and immediately releases the lock. Changes to $_SESSION afterwards are not saved. Use it for pages and API endpoints that only need to know who the user is.


Storing Sessions Elsewhere

Why Change the Storage?

The default storage keeps session files on the local disk of one server. When an application runs on several servers behind a load balancer, each request may reach a different server, which would not find the session file. Shared storage solves this.

Redis

With the phpredis extension installed, sessions can be stored in Redis by configuration alone:

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379"

Redis is fast, shared between servers, and expires old sessions automatically.

A Database Session Handler

PHP lets you implement your own storage through SessionHandlerInterface:

<?php
final class PdoSessionHandler implements SessionHandlerInterface
{
    public function __construct(private PDO $pdo) {}

    public function open(string $path, string $name): bool
    {
        return true;
    }

    public function close(): bool
    {
        return true;
    }

    public function read(string $id): string|false
    {
        $stmt = $this->pdo->prepare('SELECT data FROM sessions WHERE id = ?');
        $stmt->execute([$id]);

        return (string) ($stmt->fetchColumn() ?: '');
    }

    public function write(string $id, string $data): bool
    {
        $stmt = $this->pdo->prepare(
            'REPLACE INTO sessions (id, data, last_activity) VALUES (?, ?, ?)'
        );

        return $stmt->execute([$id, $data, time()]);
    }

    public function destroy(string $id): bool
    {
        return $this->pdo->prepare('DELETE FROM sessions WHERE id = ?')->execute([$id]);
    }

    public function gc(int $max_lifetime): int|false
    {
        $stmt = $this->pdo->prepare('DELETE FROM sessions WHERE last_activity < ?');
        $stmt->execute([time() - $max_lifetime]);

        return $stmt->rowCount();
    }
}

// Usage (before session_start):
// session_set_save_handler(new PdoSessionHandler($pdo), true);
// session_start();

With this table (MySQL):

CREATE TABLE sessions (
    id VARCHAR(128) NOT NULL PRIMARY KEY,
    data MEDIUMBLOB NOT NULL,
    last_activity INT UNSIGNED NOT NULL,
    INDEX (last_activity)
);

How It Works

  • PHP calls the handler's methods at the appropriate times: read() during session_start(), write() at the end of the request, destroy() on session_destroy(), and gc() during garbage collection.
  • $data is the serialized session; PHP handles serialization, so the handler only stores strings.
  • session_set_save_handler($handler, true) registers the handler; true ensures the session is written when the script shuts down.

Note that this simple handler does not lock sessions, so concurrent requests from the same user can overwrite each other's changes. Production-ready handlers (for example, Symfony's PdoSessionHandler) implement locking. Database sessions also make it possible to show users their active sessions and to log out a user on all devices.


Useful Session Functions

FunctionPurpose
session_start($options)Start or resume a session
session_status()Current status
session_id() / session_name()Current session ID / cookie name
session_regenerate_id(true)New ID; delete the old session data
session_write_close()Save and release the lock
session_destroy()Delete the session data from storage
session_get_cookie_params()Current cookie settings
session_set_save_handler()Use custom storage

Common Mistakes

Relying on gc_maxlifetime for Timeouts

Effect: Sessions survive longer than intended, or are deleted unpredictably.

Fix: Implement idle and absolute timeouts with timestamps.

Holding the Lock During Slow Work

Effect: Other requests from the same user wait, and pages feel slow.

Fix: Call session_write_close() early, or use read_and_close.

Using Local File Sessions on Multiple Servers

Effect: Users are randomly logged out when requests reach different servers.

Fix: Use shared storage (Redis or a database).

Storing Large Data in Sessions

Effect: Every request reads and writes the whole session, slowing the application.

Fix: Store identifiers in the session and load data when needed.

Accepting Session IDs From URLs

Effect: Session IDs leak through links, logs, and Referer headers.

Fix: Keep use_only_cookies enabled and use_trans_sid disabled.


Best Practices

  • Configure secure cookie settings and strict mode before starting sessions.
  • Implement explicit idle and absolute timeouts, and regenerate IDs at login and periodically.
  • Release session locks as soon as the session is no longer modified.
  • Use Redis or a database for sessions in multi-server deployments.
  • Keep session data small and free of secrets.

Official reference: https://www.php.net/manual/en/book.session.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions PHP Superglobals $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE PHP Type Juggling MySQL Order By MySQL Delete Data MySQL Update Data MySQL Limit Data MySQL Joins with PHP Pagination with PHP PDO Introduction PDO Connect PDO Prepared Statements PDO Fetch Modes PSR-4 Autoloading Installing Extensions (PECL / PIE) OPcache & Performance PHP-FPM Project: Contact Form with Email Project: Login & Registration System Project: CRUD App with MySQL Project: Shopping Cart with Sessions Project: REST API with PHP DateTime Class Timezones PHP Include Include vs Require PHP File Handling PHP File Open/Read PHP File Create/Write File Delete / Rename / Copy PHP Directories PHP CSV Files
Ask about this post
AI Ask about this post

Ask questions about PHP Sessions and get answers drawn from it.

Signed-in readers only.