PHP Sessions

HTTP is stateless — every request a browser sends to your server arrives with no memory of the previous one. PHP sessions solve this by letting the server keep a small, private stash of data tied to one visitor, so a shopping cart, a login state, or a multi-step form can survive across many page loads. Sessions are one of the most important tools in PHP for building anything beyond a static page.

Overview: How Sessions Work

When your script calls session_start(), PHP checks the incoming request for a cookie named PHPSESSID (the default name). If the cookie is missing — for example, on a visitor’s very first request — PHP generates a new, cryptographically random session ID and sends it back to the browser as a cookie. On every subsequent request, the browser automatically resends that cookie, and PHP uses the ID to look up the matching data on the server.

The actual session data never leaves your server. By default PHP stores it in a flat file inside the directory returned by session_save_path() (often /tmp or a path set in php.ini as session.save_path), named something like sess_<sessionid>. The contents of that file are your $_SESSION array, serialized using PHP’s internal session serialization format (similar to, but not identical to, serialize()). Only the session ID — an opaque, unguessable string — ever travels to the browser. This is what makes sessions far safer than storing sensitive data directly in cookies.

Once session_start() has run, you read and write session data through the $_SESSION superglobal, an associative array. Anything you put into it is automatically serialized back to disk at the end of the request (or whenever the session is closed), and automatically restored into $_SESSION the next time that visitor’s request calls session_start().

Sessions are separate from cookies conceptually, even though a cookie is the usual transport mechanism. A cookie just carries a small piece of text (the session ID); the session itself is the server-side storage keyed by that ID. This distinction matters: you can store arrays, objects, and large data structures in $_SESSION without worrying about cookie size limits, because none of that data is ever sent to the browser.

Syntax

<?php
session_start();

$_SESSION['key'] = 'value';
echo $_SESSION['key'];

unset($_SESSION['key']);   // remove one value
session_destroy();         // destroy the whole session
Function Purpose
session_start() Starts or resumes a session; must run before any output and before touching $_SESSION.
$_SESSION Associative array holding the session’s data for the current request.
session_id() Gets or sets the current session ID.
session_regenerate_id(bool $delete_old_session = false) Issues a fresh session ID, keeping the data. Critical after login.
session_unset() Clears all variables in $_SESSION but keeps the session open.
session_destroy() Deletes the session data on the server (does not clear $_SESSION in the current request or remove the cookie by itself).
session_write_close() Saves the session data and releases the session file lock early.

Examples

Example 1: A simple visit counter

<?php
session_start();

if (!isset($_SESSION['views'])) {
    $_SESSION['views'] = 0;
}
$_SESSION['views']++;

echo "You have visited this page " . $_SESSION['views'] . " time(s).";

Output:

You have visited this page 1 time(s).

The first time session_start() runs for a new visitor, $_SESSION is empty, so views doesn’t exist yet and gets initialized to 0 before being incremented. On the visitor’s next request, PHP restores the saved $_SESSION array (which now has views set to 1), so the counter keeps climbing across page loads without a database.

Example 2: Storing structured login data

<?php
session_start();

$_SESSION['user_id'] = 42;
$_SESSION['username'] = 'alice';
$_SESSION['roles'] = ['editor', 'subscriber'];

echo "Logged in as: " . $_SESSION['username'] . "\n";
echo "User ID: " . $_SESSION['user_id'] . "\n";
echo "Roles: " . implode(', ', $_SESSION['roles']) . "\n";

Output:

Logged in as: alice
User ID: 42
Roles: editor, subscriber

$_SESSION isn’t limited to strings — it happily stores integers, arrays, and (with some care) objects. This is exactly how most login systems work: after verifying a password, the script stores the authenticated user’s identity in $_SESSION, and every later page checks for that key to decide whether the visitor is logged in.

Example 3: One-time flash messages

<?php
session_start();

function setFlash(string $key, string $message): void {
    $_SESSION['flash'][$key] = $message;
}

function getFlash(string $key): ?string {
    if (!isset($_SESSION['flash'][$key])) {
        return null;
    }
    $message = $_SESSION['flash'][$key];
    unset($_SESSION['flash'][$key]);
    return $message;
}

setFlash('success', 'Your profile was updated.');

echo getFlash('success') . "\n";
echo var_export(getFlash('success'), true);

Output:

Your profile was updated.
NULL

“Flash” messages are a common pattern: you set a message right before a redirect (e.g. after saving a form), read it once on the next page, and it disappears automatically because getFlash() calls unset() after returning it. This avoids the message reappearing if the user refreshes the page.

How It Works Step by Step

  • The browser sends a request. PHP checks for a PHPSESSID cookie.
  • If no cookie exists, PHP generates a new random session ID and queues a Set-Cookie header (this is why session_start() must run before any HTML output — cookies are headers, and headers can’t be sent after content has started).
  • PHP locates (or creates) the matching session file on disk and unserializes its contents into the $_SESSION superglobal.
  • Your script reads and writes $_SESSION like a normal array during the request.
  • At the end of the script (or when session_write_close() is called), PHP serializes $_SESSION back to the session file and releases the file lock.
  • The next request from the same browser repeats the cycle using the same session ID, restoring the same data.

Common Mistakes

Mistake 1: Forgetting session_start()

Wrong:

<?php
$_SESSION['cart'][] = 'item-1';
echo "Cart size: " . count($_SESSION['cart']);

Without session_start(), PHP never loads (or creates) the session, so writing to $_SESSION either fails silently or only affects the current request — nothing is saved for next time. Fix it by calling session_start() first, before any output:

<?php
session_start();
$_SESSION['cart'][] = 'item-1';
echo "Cart size: " . count($_SESSION['cart']);

Mistake 2: Not regenerating the session ID after login (session fixation)

Wrong:

<?php
session_start();

function login(string $username, string $password): bool {
    $valid = $username === 'admin' && $password === 'secret';
    if ($valid) {
        $_SESSION['user'] = $username;
    }
    return $valid;
}

login('admin', 'secret');
echo $_SESSION['user'];

If an attacker can trick a victim into using a session ID the attacker already knows (session fixation), and the app never changes the ID after authentication, the attacker can hijack the now-logged-in session. Always rotate the session ID the moment privilege changes, such as right after a successful login:

<?php
session_start();

function login(string $username, string $password): bool {
    $valid = $username === 'admin' && $password === 'secret';
    if ($valid) {
        session_regenerate_id(true);
        $_SESSION['user'] = $username;
    }
    return $valid;
}

login('admin', 'secret');
echo $_SESSION['user'];

Output:

admin

Passing true to session_regenerate_id() deletes the old session file, so the pre-login ID becomes completely useless to an attacker.

Mistake 3: Stuffing too much into the session

Session data is read and unserialized on every single request that calls session_start(). Storing large objects, entire database result sets, or file contents in $_SESSION slows down every page load for that visitor and bloats the session storage. Keep session data small — IDs, flags, and small arrays — and re-fetch anything large from the database when needed.

Best Practices

  • Call session_start() as the very first line of your script, before any HTML or whitespace is output.
  • Always call session_regenerate_id(true) immediately after a successful login or any privilege change.
  • Store only small identifiers in $_SESSION (user ID, roles, flags) — fetch full records from the database when you need them.
  • Set session.cookie_httponly and session.cookie_secure in php.ini (or via session_set_cookie_params()) so the session cookie can’t be read by JavaScript and is only sent over HTTPS.
  • Call session_unset() and session_destroy() together on logout to fully clear both the in-memory array and the server-side storage.
  • Use session_write_close() as soon as you’re done writing to $_SESSION if the rest of the script does slow work, so you don’t hold the session file lock and block other concurrent requests from the same visitor.
  • Never store secrets like raw passwords or credit card numbers in the session.

Practice Exercises

  • Write a script that uses $_SESSION to remember a visitor’s preferred theme ('light' or 'dark') across requests, defaulting to 'light' the first time.
  • Build a tiny login/logout pair of functions: login() should set $_SESSION['user'] and regenerate the session ID; logout() should call session_unset() and session_destroy().
  • Extend the flash-message example to support multiple message keys at once (e.g. 'success' and 'error') and write a function that returns and clears all flash messages in one call.

Summary

  • Sessions let PHP keep server-side data tied to one visitor across multiple requests, solving HTTP’s statelessness.
  • session_start() must run before any output and before $_SESSION is used.
  • Only a random session ID travels to the browser (usually via the PHPSESSID cookie) — the actual data stays on the server.
  • Always regenerate the session ID after login to prevent session fixation attacks.
  • Keep session data small, clear it fully on logout, and secure the session cookie with the httponly and secure flags.