Introduction: "Why won't my session die?"
\n\nImagine: you're writing an online store, admin panel, or personal account. A user logs in, works, goes for coffee — and an hour later their session is still alive. Cart data is hanging, access is open. This is not only inconvenient but also dangerous. Especially if the site works with payment data.
\n\nThe question "How do I expire a PHP session after 30 minutes?" is one of the most popular on Stack Overflow (tens of thousands of views). And it's no surprise: beginner developers often think that just setting session.gc_maxlifetime is enough — and everything will work. Then they wonder why the session lives for hours.
In this article, we'll break down 4 real approaches to make a PHP session expire exactly after 30 minutes. You'll learn why standard settings can fail, and which method to choose for your task. Let's go!
\n\nA bit of theory: how PHP manages session time
\n\nBefore writing code, it's important to understand two mechanisms that affect session "death":
\n\n- \n
- Session cookie lifetime — how long the browser stores the session ID (PHPSESSID). Managed by the
session.cookie_lifetimeparameter. \n - Server-side data lifetime — how long the session file stays in the /tmp folder. Managed by
session.gc_maxlifetime. \n
The problem is that the garbage collector (GC) runs probabilistically. By default, it fires on 1% of requests. This means that even if gc_maxlifetime = 1800 (30 minutes), the session can hang for another hour until the GC gets to it.
Therefore, relying only on the server setting is a bad idea. You need to check the time "manually".
\n\nMethod #1. Classic: checking time in the session (recommended)
\n\nThis is the most reliable and transparent method. You store a timestamp of the last activity in the session yourself, and on each request, you check if 30 minutes have passed.
\n\n<?php\nsession_start();\n\n// Set session lifetime — 30 minutes (1800 seconds)\n$timeout_duration = 1800;\n\n// If there is a timestamp in the session and it is expired\nif (isset($_SESSION['LAST_ACTIVITY']) && \n (time() - $_SESSION['LAST_ACTIVITY']) > $timeout_duration) {\n // Destroy the session\n session_unset();\n session_destroy();\n // Redirect to the login page\n header("Location: login.php?timeout=1");\n exit;\n}\n\n// Update the timestamp (if the user is active)\n$_SESSION['LAST_ACTIVITY'] = time();\n?>\n\nPros:
\n- \n
- Absolute control: the session will die exactly after 30 minutes of inactivity. \n
- Does not depend on PHP settings and the garbage collector. \n
- Easy to change the time for different site sections (e.g., admin panel — 15 minutes, user — 60). \n
Cons:
\n- \n
- You need to add the check to every page (or put it in a common initialization file). \n
- If the user keeps the page open but doesn't click — the session will die. For some scenarios (long forms), this is a downside. \n
Method #2. Combined: gc_maxlifetime + cookie_lifetime
\n\nIf you don't want to write a manual check, you can try configuring PHP "out of the box". But remember the nuances.
\n\n// At the beginning of the script, before session_start()\n$lifetime = 1800; // 30 minutes\n\n// Set the session cookie lifetime\nsession_set_cookie_params($lifetime);\n\n// Set the maximum server-side data lifetime\nini_set('session.gc_maxlifetime', $lifetime);\n\n// Now start the session\nsession_start();\n\nAdditionally — to make the GC run more often, you can reduce the probability of its launch:
\n\nini_set('session.gc_probability', 1);\nini_set('session.gc_divisor', 1); // GC will run on every request