databases storage and caching

Session Storage Backends

Sessions let a PHP application remember state between HTTP requests. The browser usually keeps a small session ID cookie, while the actual session data lives on the server in a storage backend such as files, Redis, Memcached, or a database.

The main skill is choosing storage that matches the deployment. A single-server application can often use file sessions. A load-balanced application needs a backend every app server can reach, or users may appear logged out when their next request lands on a different server.

What A Session Stores

Session data should be small and temporary. It is useful for values such as a logged-in user ID, CSRF token, flash message, or checkout step. It is not a place for whole user profiles, shopping catalogues, large API responses, or source-of-truth business data.

PHP example
<?php

declare(strict_types=1);

function sessionPayloadForLogin(int $userId): array
{
    return [
        'user_id' => $userId,
        'authenticated_at' => time(),
        'flash_messages' => [],
    ];
}

$payload = sessionPayloadForLogin(42);

echo $payload['user_id'] . PHP_EOL;
echo array_key_exists('flash_messages', $payload) ? 'has flash storage' : 'missing flash storage';
echo PHP_EOL;

// Prints:
// 42
// has flash storage

The session should usually store an ID, then load fresh user details from the database when needed. That avoids stale roles, stale account status, and oversized sessions.

The browser cookie normally stores only the session ID. The server uses that ID to read the session payload from the backend.

PHP example
<?php

declare(strict_types=1);

function sessionLookupPlan(string $sessionId, string $backend): string
{
    return sprintf('Read session %s from %s storage.', $sessionId, $backend);
}

echo sessionLookupPlan('abc123', 'Redis') . PHP_EOL;

// Prints:
// Read session abc123 from Redis storage.

This is why stealing a session cookie is serious: the cookie can point to server-side authenticated state. Protect it with Secure, HttpOnly, and an appropriate SameSite setting.

Common Backends

File sessions are PHP's common default. They are simple and fine for one server, but they do not naturally work across multiple app servers unless the files are on shared storage.

Redis sessions are common for production applications with several app servers. Redis is fast, centralised, and supports expiry well. It is often the strongest default for horizontally scaled PHP apps.

Database sessions are useful when the team wants persistence and queryability, or when Redis is not available. They are usually slower than Redis and can add write load to the main database.

Memcached sessions can work for temporary session data, but the team must be comfortable with eviction behaviour. If losing a session unexpectedly would be unacceptable, choose carefully.

PHP example
<?php

declare(strict_types=1);

function recommendSessionBackend(int $appServerCount, bool $redisAvailable): string
{
    if ($appServerCount === 1) {
        return 'files';
    }

    if ($redisAvailable) {
        return 'redis';
    }

    return 'database';
}

echo recommendSessionBackend(3, true) . PHP_EOL;

// Prints:
// redis

The backend choice is partly technical and partly operational. It depends on what the team can run, monitor, back up, and debug.

Load Balancers And Sticky Sessions

Some load balancers support sticky sessions, where the same user is sent to the same app server. Sticky sessions can make file sessions appear to work on multiple servers, but they are fragile. If a server fails or traffic is rebalanced, users can lose access to the file that held their session.

A shared backend is usually clearer. Any app server can handle the next request because all servers read from the same session store.

PHP example
<?php

declare(strict_types=1);

function sessionReachabilityCheck(string $backend, int $appServerCount): string
{
    if ($backend === 'files' && $appServerCount > 1) {
        return 'risk: file sessions are local unless storage is shared';
    }

    return 'ok: every app server can use the session backend';
}

echo sessionReachabilityCheck('files', 4) . PHP_EOL;

// Prints:
// risk: file sessions are local unless storage is shared

This is one of the most common real deployment mistakes with PHP sessions.

Expiry And Garbage Collection

Sessions need expiry. Old sessions should not live forever, and logged-out sessions should be removed or invalidated.

With Redis, expiry is usually handled with key TTLs. With database sessions, the application often stores a last_activity or expires_at column and deletes old rows. With file sessions, PHP has session garbage collection settings, but the exact behaviour depends on configuration and traffic.

PHP example
<?php

declare(strict_types=1);

function sessionExpirySeconds(int $minutes): int
{
    return $minutes * 60;
}

echo sessionExpirySeconds(120) . PHP_EOL;

// Prints:
// 7200

Expiry should match the product's security needs. Admin areas, payment flows, and shared devices often need stricter rules than low-risk browsing sessions.

Locking And Slow Requests

PHP sessions are often locked while a request is using them. That prevents two requests from writing conflicting session data at the same time, but it can also block concurrent requests from the same user.

For example, if one request starts the session and then performs a slow export, another tab may wait before it can read or write the same session. Frameworks and session handlers vary, but the lesson is practical: do not keep session writes open longer than needed.

PHP example
<?php

declare(strict_types=1);

function sessionWriteAdvice(bool $requestIsSlow): string
{
    if ($requestIsSlow) {
        return 'write the needed session changes early, then release the session before slow work';
    }

    return 'keep the session small and finish the request normally';
}

echo sessionWriteAdvice(true) . PHP_EOL;

// Prints:
// write the needed session changes early, then release the session before slow work

In plain PHP, session_write_close() can release the session lock after changes are saved. In a framework, use the framework's session lifecycle conventions.

Regenerate Session IDs After Login

After a user logs in, the session ID should be regenerated. This reduces the risk of session fixation, where an attacker tries to make a victim use a known session ID before authentication.

PHP example
<?php

declare(strict_types=1);

function loginSessionActions(): array
{
    return [
        'regenerate_session_id' => true,
        'store_user_id' => true,
        'clear_old_flash_messages' => true,
    ];
}

print_r(loginSessionActions());

// Prints:
// [regenerate_session_id] => 1
// [store_user_id] => 1

The exact call in plain PHP is session_regenerate_id(true), after the session has started and before the authenticated state is trusted.

Session cookie settings matter as much as the backend. For normal HTTPS applications, the session cookie should usually be secure, HTTP-only, and same-site.

PHP example
<?php

declare(strict_types=1);

function recommendedSessionCookieOptions(): array
{
    return [
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Lax',
        'path' => '/',
    ];
}

print_r(recommendedSessionCookieOptions());

// Prints:
// [secure] => 1
// [httponly] => 1
// [samesite] => Lax

Secure means the cookie is sent over HTTPS. HttpOnly prevents JavaScript from reading it. SameSite helps control cross-site request behaviour.

What Not To Store In Sessions

Avoid storing:

  • passwords, access tokens, or long-lived secrets;
  • full user records that can become stale;
  • large arrays of product or report data;
  • anything needed as a durable audit trail;
  • permission decisions that should be checked fresh.

Store small identifiers and short-lived UI state. Load important data from the proper source when needed.

What To Check

Before moving on, make sure you can:

  • explain that the cookie usually stores a session ID, not the whole session;
  • choose file sessions for simple single-server apps and shared storage for scaled apps;
  • explain why Redis is commonly used for multi-server PHP sessions;
  • describe how database sessions differ from Redis sessions;
  • set sensible expiry and cleanup rules;
  • recognise session locking problems in slow requests;
  • regenerate the session ID after login;
  • protect session cookies with secure options.

Practice

Practice: Choose A Session Storage Backend

Build a small PHP helper that recommends a session storage backend and cookie settings for a PHP application.

Requirements

  • Choose files for a single app server.
  • Choose redis for multiple app servers when Redis is available.
  • Choose database as the fallback when multiple app servers need shared sessions and Redis is not available.
  • Warn when file sessions are used with more than one app server.
  • Include a TTL in seconds.
  • Return secure session cookie options.
  • Show one single-server example and one multi-server example.

Focus on the deployment decision. You do not need to start a real PHP session or connect to Redis.

Show solution

This solution models the backend choice separately from the framework or PHP configuration that would apply it.

PHP example
<?php

declare(strict_types=1);

function chooseSessionBackend(int $appServerCount, bool $redisAvailable): string
{
    if ($appServerCount === 1) {
        return 'files';
    }

    if ($redisAvailable) {
        return 'redis';
    }

    return 'database';
}

function sessionStoragePlan(int $appServerCount, bool $redisAvailable, int $ttlMinutes): array
{
    $backend = chooseSessionBackend($appServerCount, $redisAvailable);
    $warnings = [];

    if ($backend === 'files' && $appServerCount > 1) {
        $warnings[] = 'file sessions are local unless the session directory is shared';
    }

    return [
        'backend' => $backend,
        'ttl_seconds' => $ttlMinutes * 60,
        'warnings' => $warnings,
        'cookie' => [
            'secure' => true,
            'httponly' => true,
            'samesite' => 'Lax',
            'path' => '/',
        ],
    ];
}

$singleServer = sessionStoragePlan(1, false, 120);
$multiServer = sessionStoragePlan(4, true, 60);

echo $singleServer['backend'] . PHP_EOL;
echo $singleServer['ttl_seconds'] . PHP_EOL;
echo $multiServer['backend'] . PHP_EOL;
echo $multiServer['cookie']['samesite'] . PHP_EOL;

// Prints:
// files
// 7200
// redis
// Lax

The recommendation is based on reachability. A user may hit any app server on the next request, so a multi-server application needs session storage that every app server can read.