PHP Development

Master PHP Sessions with Ease: A Comprehensive Developers Handbook

By the Domain India teamPublished 9 min read
Knowledge base article
Contents (10 sections)

PHP sessions let your site remember a visitor between page loads: who is logged in, what is in the cart, which step of a form they reached. This handbook covers how sessions work, the settings that make them secure, and the problems developers hit most often, with PHP 8 code you can use as it is.

Key takeaways

Call session_start() before any output, with the cookie set to Secure, HttpOnly and SameSite=Lax and session.use_strict_mode on. Call session_regenerate_id(true) every time a user logs in or their privileges change, and clear the session properly at logout. Keep only small values such as a user ID in $_SESSION, add an idle timeout, and close the session early on slow pages so other requests are not blocked.

1. How a PHP session works

HTTP forgets everything between requests. A session fixes that in three steps:

  1. session_start() runs.
    PHP creates a long random session ID, or reads the one the browser sent.
  2. PHP sends the ID in a cookie
    , named PHPSESSID by default. The browser returns it with every request.
  3. The data stays on the server.
    Everything you put in $_SESSION is saved on the server under that ID, and loaded again on the next request.

The visitor only ever holds the ID. That is why the ID is the thing to protect: anyone who steals it becomes that user until the session ends.

2. A secure session start

Put this in one file, for example session.php, and include it at the top of every page that uses sessions.

php
<?php
declare(strict_types=1);

function start_secure_session(): void
{
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }

    ini_set('session.use_strict_mode', '1');   // refuse IDs the server did not create
    ini_set('session.use_only_cookies', '1');  // never accept an ID from the URL

    session_name('app_session');
    session_set_cookie_params([
        'lifetime' => 0,        // cookie ends when the browser closes
        'path'     => '/',
        'secure'   => true,     // only sent over HTTPS
        'httponly' => true,     // JavaScript cannot read it
        'samesite' => 'Lax',    // not sent on cross-site POSTs
    ]);

    session_start();
}

What each setting protects against:

  • use_strict_mode stops session fixation, where an attacker plants a known ID in a victim's browser and waits for them to log in.
  • Secure keeps the cookie off plain HTTP, where it could be read in transit. Your whole site must run on HTTPS.
  • HttpOnly means an injected script cannot read document.cookie and steal the ID.
  • SameSite=Lax blocks the cookie on most cross-site form posts. Use Strict for admin areas if you can live with users arriving logged out from external links.

session_start() sends a cookie header, so it must run before any HTML, blank line or echo. Otherwise you get "headers already sent".

3. Storing and removing data

php
start_secure_session();

$_SESSION['user_id'] = 42;              // store
$userId = $_SESSION['user_id'] ?? null; // read safely
unset($_SESSION['flash_message']);      // remove one value

Keep session data small and boring: an ID, a role, a cart of product IDs and quantities. Load everything else from the database when you need it. Never store a password, a card number or an API secret in the session.

4. Login, logout and session IDs

The two moments that matter most are login and logout.

php
// After the password has been verified
function log_in(int $userId): void
{
    session_regenerate_id(true);        // new ID, old session deleted
    $_SESSION['user_id']       = $userId;
    $_SESSION['logged_in_at']  = time();
    $_SESSION['last_activity'] = time();
}

function log_out(): void
{
    $_SESSION = [];
    $p = session_get_cookie_params();
    setcookie(session_name(), '', [
        'expires'  => time() - 3600,
        'path'     => $p['path'],
        'domain'   => $p['domain'],
        'secure'   => $p['secure'],
        'httponly' => $p['httponly'],
        'samesite' => $p['samesite'],
    ]);
    session_destroy();
}
  • Regenerate the ID at login, at logout and whenever privileges change, such as entering an admin area. Passing true deletes the old session, so a stolen pre-login ID becomes useless.
  • Log out properly. session_destroy() on its own leaves $_SESSION filled for the rest of the request and leaves the cookie in the browser. Clear all three.
  • Make logout a POST request with a CSRF token, not a plain link, so another site cannot log your users out. See CSRF tokens in PHP forms.

5. Timeouts

PHP's own clean-up (session.gc_maxlifetime) runs on a probability and is not a reliable timeout. Enforce one yourself:

php
const IDLE_LIMIT = 1800;       // 30 minutes without activity
const ABSOLUTE_LIMIT = 28800;  // 8 hours after login, whatever happens

$now = time();
if (isset($_SESSION['user_id'])) {
    $idle = $now - ($_SESSION['last_activity'] ?? 0);
    $age  = $now - ($_SESSION['logged_in_at'] ?? $now);
    if ($idle > IDLE_LIMIT || $age > ABSOLUTE_LIMIT) {
        log_out();
        header('Location: /login.php?expired=1');
        exit;
    }
    $_SESSION['last_activity'] = $now;
}

Banking-style sites use short limits; a blog can use days.

6. Session locking and slow pages

With the default file storage, PHP locks the session file from session_start() until the script ends. A second request from the same user, such as an AJAX call, waits until the first one finishes. One slow page can make the whole site feel frozen for that user.

Release the lock as soon as you have finished writing:

php
start_secure_session();
$userId = $_SESSION['user_id'] ?? null;
session_write_close();          // lock released; $_SESSION is now read-only

// slow work here: reports, API calls, file generation

For pages that only read the session, start it with session_start(['read_and_close' => true]);, which reads the data and releases the lock immediately. Call session_set_cookie_params() and the ini_set() lines from section 2 first, as the helper does.

7. "Remember me" done safely

Never put a user ID or a password in a long-lived cookie. Use a random token pair:

  1. Create two random values
    at login: a selector (bin2hex(random_bytes(12))) and a validator (bin2hex(random_bytes(32))).
  2. Store the selector and a hash of the validator
    (hash('sha256', $validator)) in a database table with the user ID and an expiry date.
  3. Send selector:validator
    in a cookie with Secure, HttpOnly and SameSite=Lax, lasting, say, 30 days.
  4. On a later visit with no session,
    look up the selector, hash the validator from the cookie, compare with hash_equals(), and if it matches, log the user in, delete that token and issue a new one.
  5. Delete all of a user's tokens
    when they log out everywhere or change their password.

8. Common problems and fixes

  • "Headers already sent": something was output before session_start(). Look for a blank line before <?php, a closing ?> followed by whitespace in an included file, or a UTF-8 byte order mark. Leave off the closing ?> in PHP-only files. See PHP session and header errors.
  • Session data disappears between pages: check the cookie is being set (browser developer tools, Application › Cookies). A Secure cookie is not sent over plain HTTP, and a cookie path or domain that does not match the page is not sent either. Moving between www and non-www counts as a different host.
  • Users logged out at random: another application on the same domain may be using the default PHPSESSID name and a different save path. Give each application its own session_name().
  • Everything slows down on AJAX-heavy pages: session locking; see section 6.

9. Frameworks

In Laravel, Symfony or CodeIgniter, use the framework's session layer instead of calling session_start() yourself. The same rules apply to their configuration: secure cookie flags, a new ID at login, and a sensible lifetime.

10. Running PHP sessions on Domain India

PHP sessions work normally on all Domain India shared hosting with the default file storage, so the code above runs as it is. Free SSL is included, which the Secure flag needs. On cPanel you choose the PHP version per domain, and new accounts start on PHP 8.3 (measured September 2026). You can set session.* options with ini_set() as shown, or with a .user.ini file in your site's folder. Do not put php_value lines in .htaccess: our servers run PHP through PHP-FPM or CGI, and that line causes a 500 error.

Shared hosting has no Redis or Memcached for session storage. If you need sessions shared across several servers, store them in your MySQL database with a custom SessionHandlerInterface, or move to a VPS, where you run Redis yourself.

The card shows the live monthly price, excluding 18% GST.

cPanel Starter
₹125/mo + GST
  • 25 GB NVMe SSD Storage
  • 50 GB Monthly Bandwidth
  • 1 Website
  • 10 Email Accounts
See plan details
When should I call session_regenerate_id()?

Call session_regenerate_id(true) whenever a user logs in, logs out or gains extra privileges, such as entering an admin area. Passing true deletes the old session, so an ID that was stolen or planted before login stops working.

What cookie settings should a PHP session use?

Use Secure so the cookie is only sent over HTTPS, HttpOnly so JavaScript cannot read it, and SameSite=Lax so it is not sent on most cross-site form posts. Also turn on session.use_strict_mode and session.use_only_cookies.

How do I log a user out completely in PHP?

Empty $_SESSION, expire the session cookie with setcookie() using the same path and domain, and call session_destroy(). Calling session_destroy() alone leaves the cookie in the browser and the data in $_SESSION for the rest of the request.

How do I make a PHP session expire after inactivity?

Store the time of the last request in the session, compare it with the current time at the start of each request, and log the user out if the gap is longer than your limit, for example 30 minutes. Do not rely on session.gc_maxlifetime, which only cleans up old files on a probability.

Why are my PHP pages slow when several AJAX requests run at once?

File-based sessions are locked while a script runs, so requests from the same user wait for each other. Call session_write_close() as soon as you have finished changing the session, or open it with session_start(['read_and_close' => true]) on pages that only read it.

Can I use Redis for PHP sessions on shared hosting?

Not on Domain India shared hosting, which has no Redis service. The default file sessions work, or you can store sessions in MySQL with a custom handler. For Redis, use a VPS, where you install and run it yourself.

Ready to build your login system? Follow Building a user registration system with PHP and MySQL, protect your forms with CSRF tokens, and host it on cPanel hosting or DirectAdmin hosting.

Host your PHP application

Choose your PHP version per domain, with free SSL for secure session cookies.

See cPanel plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app