Menu

PHP Sessions and Cookies: session_start and setcookie

Call session_start() at the top of every page, then store values in $_SESSION['key']; PHP keeps them on the server and remembers the visitor through a cookie. setcookie('name', 'value', $options) stores a small value in the browser, read back from $_COOKIE. Learn login, logout, flash messages, cookie options and the security settings.

This page includes runnable editors - edit, run, and see output instantly.

Call session_start() at the top of a page, then read and write $_SESSION['key'] like any array: PHP saves it on the server and finds it again on the visitor's next request. For a small value the browser should keep, call setcookie('name', 'value', time() + 86400) and read it back on later requests from $_COOKIE['name'].

<?php
session_start();                        // before any output
setcookie('theme', 'dark', time() + 60 * 60 * 24 * 30);   // 30 days, also before output

$_SESSION['visits'] = ($_SESSION['visits'] ?? 0) + 1;
$theme = $_COOKIE['theme'] ?? 'light';  // set on an earlier visit
echo "You have opened this page {$_SESSION['visits']} times.";

Sessions and cookies only make sense across several HTTP requests from a browser, so the examples that need one are plain code. The logic around them is ordinary PHP on arrays, and that part runs here: in each runnable block, $session stands in for $_SESSION, and every call is one request.

How a PHP session works

  1. The first session_start() creates a random id, sends it to the browser in a cookie named PHPSESSID, and gives you an empty $_SESSION.
  2. At the end of the request PHP saves $_SESSION on the server (in files by default, or Redis, or a database).
  3. On the next request the browser sends the cookie back, session_start() reads the id and loads the saved array.

The browser never sees the data, only the id. That is why the logged-in user's id belongs in $_SESSION and never in a cookie of your own: a visitor can edit their cookies to any value, but they cannot edit $_SESSION.

Log a user in and out with a session

Logging in is: check the password, regenerate the session id, store the user id. Logging out is the reverse, and it has three steps.

<?php
// login.php
session_start();

$user = find_user_by_email($_POST['email'] ?? '');
if ($user && password_verify($_POST['password'] ?? '', $user['password_hash'])) {
    session_regenerate_id(true);        // new id after login: stops session fixation
    $_SESSION['user_id'] = $user['id'];
    header('Location: /dashboard.php');
    exit;
}
$error = 'Wrong email or password.';
<?php
// any protected page
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: /login.php');
    exit;
}
<?php
// logout.php
session_start();
$_SESSION = [];                                     // 1. forget the data

$p = session_get_cookie_params();                   // 2. delete the cookie
setcookie(session_name(), '', time() - 3600, $p['path'], $p['domain'], $p['secure'], $p['httponly']);

session_destroy();                                  // 3. delete the stored session
header('Location: /');
exit;

session_regenerate_id(true) matters more than it looks. If an attacker can make a victim use a session id the attacker knows (for example through a link), and the id stays the same after login, the attacker is logged in too. A fresh id at login closes that hole. Password checking itself is covered on the password_hash page.

Expire a session after inactivity

PHP's own cleanup (session.gc_maxlifetime, 1440 seconds by default) runs at random and is not a reliable timeout. Store the time of the last request and check it yourself. The block uses fixed timestamps so you can see each case; change $timeout or the times.

The gap from 09:05 to 09:16:40 is 700 seconds, under the 15 minute limit, so the user stays in. The next gap is 1000 seconds, so the session is cleared.

Flash messages

A flash message is set on one request and shown once on the next, typically "Saved." after a redirect. Store it in the session, then read and remove it in one step:

setcookie() takes the name, the value and an options array (PHP 7.3 and later). Set these four options on every cookie unless you have a reason not to:

<?php
setcookie('theme', 'dark', [
    'expires'  => time() + 60 * 60 * 24 * 30,   // 30 days; 0 means "until the browser closes"
    'path'     => '/',                          // send it on every page, not just this folder
    'secure'   => true,                         // only over HTTPS
    'httponly' => true,                         // JavaScript cannot read it
    'samesite' => 'Lax',                        // not sent on most cross-site requests
]);

$theme = $_COOKIE['theme'] ?? 'light';

Two things trip people up:

  • $_COOKIE does not change in the same request. It holds what the browser sent with this request. A cookie you set now shows up in $_COOKIE on the next one.
  • Cookies are headers, so setcookie() must run before any output, exactly like session_start() and header().

To delete a cookie, set it again with the same name and path and an expiry in the past: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']).

A cookie value is a string of at most about 4 KB, so store structured data as JSON, and treat what comes back as untrusted: the visitor can change it. Never unserialize() a cookie: it can create objects of your own classes and run their magic methods, while json_decode() only ever returns arrays, strings, numbers, booleans, null or stdClass.

The json_encode page covers the flags. For anything larger than a few short values, keep the data in the session (or a database) and store only a key in the cookie.

"Remember me" tokens

A "remember me" cookie must not hold the user id or the password hash. Store a long random token in the cookie and only its hash in the database, and compare with hash_equals():

The values change on every run, because random_bytes() is random. If the database leaks, the hashes are useless without the cookies, and hash_equals() compares in constant time so the check does not leak timing information.

Session ($_SESSION)Cookie ($_COOKIE)
StoredOn the serverIn the browser
The visitor can read or edit itNo (only the id)Yes
SizeLimited by your storageAbout 4 KB per cookie
LastsUntil the browser closes or the session expiresUntil its expires time
Use forLogged-in user, cart, CSRF token, flash messagesTheme, language, consent choice, "remember me" token

Common mistake: headers already sent

session_start(), setcookie() and header() all send HTTP headers, and headers can only be sent before the body. Any output first, even a blank line before <?php in an included file, produces:

Warning: session_start(): Session cannot be started after headers have already been sent in /var/www/page.php on line 3
Warning: Cannot modify header information - headers already sent by (output started at /var/www/page.php:2) in /var/www/page.php on line 4

The second message tells you where the output started (page.php:2): go to that line and remove the output, or move session_start() above it. In files that contain only PHP, leaving out the closing ?> prevents stray whitespace after it from becoming output.

Frequently Asked Questions

What is the difference between a session and a cookie in PHP?

A cookie is stored in the visitor's browser and sent with every request, so the visitor can read and change it. A session stores its data on the server; the browser only holds a random session id in a cookie (PHPSESSID). Put anything that must be trusted, like the logged-in user id, in $_SESSION.

How do I destroy a session on logout in PHP?

Empty the data, delete the session cookie and destroy the stored session: $_SESSION = [];, then setcookie(session_name(), '', time() - 3600, '/'), then session_destroy();. Redirect afterwards so the next page starts fresh.

How do I delete a cookie in PHP?

Set it again with the same name, path and domain and an expiry in the past: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);. Also unset($_COOKIE['theme']) if the rest of the current script reads it, because $_COOKIE only changes on the next request.

How long does a PHP session last?

By default the session cookie lasts until the browser closes (session.cookie_lifetime = 0) and the server may delete session data after 1440 seconds (24 minutes) without activity (session.gc_maxlifetime). For a fixed timeout, store the time of the last request in the session and check it yourself.

Why does session_start() say headers already sent?

The session id travels in a cookie header, and headers must be sent before any output. An echo, HTML, or even a blank line or BOM before <?php sends the output first. Call session_start() at the very top of the script, before anything is printed.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED