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
- The first
session_start()creates a random id, sends it to the browser in a cookie namedPHPSESSID, and gives you an empty$_SESSION. - At the end of the request PHP saves
$_SESSIONon the server (in files by default, or Redis, or a database). - 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:
Set a cookie with setcookie
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:
$_COOKIEdoes not change in the same request. It holds what the browser sent with this request. A cookie you set now shows up in$_COOKIEon the next one.- Cookies are headers, so
setcookie()must run before any output, exactly likesession_start()andheader().
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' => '/']).
Store an array in a cookie
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 vs cookie: which to use
Session ($_SESSION) | Cookie ($_COOKIE) | |
|---|---|---|
| Stored | On the server | In the browser |
| The visitor can read or edit it | No (only the id) | Yes |
| Size | Limited by your storage | About 4 KB per cookie |
| Lasts | Until the browser closes or the session expires | Until its expires time |
| Use for | Logged-in user, cart, CSRF token, flash messages | Theme, 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.