Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

PHP (8.5)  Module 1 – Start here: PHP in 2026

What PHP is and where it runs in 2026

How PHP runs from the command line and behind web servers, why every request starts with empty memory, and which PHP branches still get security fixes.

  • Beginner
  • 15 minutes
  • Examples run with PHP 8.5.11 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain how the php command and a web server start the same script, and why each web request starts with empty memory
  • Choose a way to serve PHP on the web from PHP-FPM, the Apache module, the built-in server and FrankenPHP
  • Find which PHP branch a site runs and check whether that branch still receives security fixes
  • Identify the PHP version, system, integer size and SAPI behind a recorded run
What you’ll learn

What you will learn

  • Explain how the php command and a web server start the same script, and why each web request starts with empty memory
  • Choose a way to serve PHP on the web from PHP-FPM, the Apache module, the built-in server and FrankenPHP
  • Find which PHP branch a site runs and check whether that branch still receives security fixes
  • Identify the PHP version, system, integer size and SAPI behind a recorded run
On this page

PHP is the server-side language of 69.4 % of the websites whose language W3Techs can identify, and by the same survey about half of those PHP sites run a branch that no longer receives security fixes. Anyone who looks after a PHP site needs to know three things: how PHP runs their code, what PHP keeps from one request to the next, and which branch they run.

This lesson runs one small script from the command line and through a web server and watches what changes. It then compares the common ways to serve PHP, works out which branches still get fixes, and shows how to read the PHP behind every recorded run in this track.

One PHP engine, started by different programs

Type php hello.php in a terminal and PHP runs the file once, prints what it echoes and stops. Put the same file behind a web server and PHP runs it once for every request, and what it echoes becomes the page. Both times the same engine compiles and runs the script. What differs is the program around it that starts the engine and hands it its input, which PHP calls a SAPI: Server API: the part of PHP that connects its engine to the program that starts it, such as the php command (cli), PHP-FPM (fpm-fcgi), the Apache module or the built-in web server (cli-server). PHP_SAPI holds its name., short for Server API.

The php command is the CLI SAPI. It runs scripts, scheduled jobs and tools such as Composer. Web servers reach PHP through other SAPIs: PHP-FPM, the Apache module, the built-in development server and application servers such as FrankenPHP. A script can ask which one started it, because the constant PHP_SAPI holds its name. This program prints that and the other facts a run depends on:

Which PHP runs this script? PHP · version_check.php
<?php

// Which PHP runs this script, on which system, and through which SAPI?
printf("PHP version    %s\n", PHP_VERSION);
printf("Branch         %d.%d\n", PHP_MAJOR_VERSION, PHP_MINOR_VERSION);
printf("System         %s\n", PHP_OS_FAMILY);
printf("Integer size   %d bits\n", PHP_INT_SIZE * 8);
printf("SAPI           %s\n", PHP_SAPI);

Output

PHP version    8.5.11
Branch         8.5
System         Darwin
Integer size   64 bits
SAPI           cli

Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php version_check.php

The php command ran it, so the SAPI is cli. The next session runs one small file twice: first with the php command, then through the built-in web server: The development web server that php -S starts. It runs scripts through the cli-server SAPI, logs each request in its terminal and must not be used on a public network. More in “Your first PHP script, the CLI and error messages” that php -S starts. A second terminal asks that server for the page with curl, a command-line program that makes web requests:

public/sapi.php PHP · public/sapi.php
<?php

echo 'Run by the ', PHP_SAPI, " SAPI\n";
echo 'Request method: ', $_SERVER['REQUEST_METHOD'] ?? 'none, no web request', "\n";
One file, run by two SAPIs Python · sapi_session.py
# The commands of this session, as you would type them in a terminal; the output shows what they printed.
from session import run, serve

run("php public/sapi.php")

serve("php -S 127.0.0.1:8471 -t public", """
curl -s http://127.0.0.1:8471/sapi.php
curl -s -I http://127.0.0.1:8471/sapi.php
""")

Output

$ php public/sapi.php
Run by the cli SAPI
Request method: none, no web request
# Terminal 1: start the server
$ php -S 127.0.0.1:8471 -t public
# Terminal 2: send requests
$ curl -s http://127.0.0.1:8471/sapi.php
Run by the cli-server SAPI
Request method: GET
$ curl -s -I http://127.0.0.1:8471/sapi.php
HTTP/1.1 200 OK
Host: 127.0.0.1:8471
Date: …
Connection: close
X-Powered-By: PHP/8.5.11
Content-type: text/html; charset=UTF-8
# Terminal 1 showed this log, then Ctrl+C stopped the server:
[…] PHP 8.5.11 Development Server (http://127.0.0.1:8471) started
[…] 127.0.0.1:<port> Accepted
[…] 127.0.0.1:<port> [200]: GET /sapi.php
[…] 127.0.0.1:<port> Closing
[…] 127.0.0.1:<port> Accepted
[…] 127.0.0.1:<port> [200]: HEAD /sapi.php
[…] 127.0.0.1:<port> Closing

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sapi_session.py

The file did not change, but its surroundings did. Under the built-in server PHP_SAPI is cli-server, and the An array that PHP fills before a script starts, with facts about how it was started: under a web SAPI the request (its method, path and headers), on the command line the script's name and its arguments. array describes the request: its REQUEST_METHOD is GET, because a web request came with the run. On the command line there was no request, so the The null coalescing operator: a ?? b gives the value of a when it exists and is not null, and b otherwise, without a warning about a missing key. printed the text on its right. The server logged each connection in its own terminal, and the HEAD request that curl -I sends, which asks for the headers alone, shows the headers the server sent, among them X-Powered-By: PHP/8.5.11. Step through the two runs:

Interactive · step by step One file, started by two SAPIs Step through the session above. The same file runs once from the terminal and once for a web request.
One file, started by two SAPIs, step 6 of 6. Showing Your terminal, curl in terminal 2, php, php -S, PHP engine (PHP_SAPI is cli-server), Server log ([200]: GET /sapi.php). In focus: curl in terminal 2, Server log.Your terminalphp public/sapi.phpcurl in terminal 2GET /sapi.phpphpthe CLI SAPIphp -Sthe cli-server SAPIPHP enginePHP_SAPI is cli-serverServer log[200]: GET /sapi.phpthe page

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Two ways in. The session runs one file, public/sapi.php, twice. The terminal on the left starts it as a program; curl on the right asks a web server for it.
  2. The php command. Typing php public/sapi.php starts the CLI SAPI. It reads the file and hands it to the engine with no web request around it.
  3. The engine runs the file. The engine compiles the file and runs it. PHP_SAPI is cli, and $_SERVER has no REQUEST_METHOD, so the fallback text after ?? is printed.
  4. A web request. In a second terminal, curl sends GET /sapi.php to the built-in server started with php -S 127.0.0.1:8471 -t public.
  5. The same file, a new SAPI. php -S finds sapi.php in its document root, public, puts the request into $_SERVER and starts the engine on the same file.
  6. The answer and the log. The engine prints the cli-server SAPI and GET. The server sends that back as the response body and writes the request to its log in its own terminal.

Other web SAPIs report their own names: fpm-fcgi under PHP-FPM and apache2handler under the Apache module. The manual’s page on the command line recommends exactly this test, the SAPI’s name, when a script must know whether it runs from a terminal.

Checkpoint

Check: One engine, several SAPIs

  1. Question 1 of 2 The built-in server was started with php -S 127.0.0.1:8471 -t public, and a browser asks it for /sapi.php. What does echo PHP_SAPI; in that file print?

    Choose one answer.

    A hint

    The session above printed the SAPI for both runs.

    Show the answer: Question 1 of 2

    Answer: cli-server

    PHP_SAPI names the program that started the engine. The same file printed cli when the php command ran it and cli-server when the built-in server ran it for a web request.

  2. Question 2 of 2 Run with php public/sapi.php, the script printed "Request method: none, no web request". Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The CLI SAPI starts the script without a web request, so $_SERVER has no REQUEST_METHOD and ?? prints its right side

    The input a script gets comes from its SAPI. A web SAPI puts the request into $_SERVER; the CLI SAPI has no request to describe, so the ?? operator fell back to the text on its right.

Every web request starts with empty memory

Ask a PHP page for the same address three times, and a counter in it might be expected to reach 3. Under PHP-FPM, the Apache module and the built-in server it never does, because each request runs the script from its first line with nothing left from earlier requests. This script tries to count in three places, a variable, a static variable inside a function and a file:

public/counter.php PHP · public/counter.php
<?php

// If PHP kept memory from one request to the next, these two would count up.
$visits = ($visits ?? 0) + 1;

function calls(): int
{
    static $count = 0;

    return ++$count;
}

// A file outlives the request that wrote it.
$file = __DIR__ . '/../visits.txt';
$saved = (is_file($file) ? (int) file_get_contents($file) : 0) + 1;
file_put_contents($file, (string) $saved);

echo "variable: $visits, static: " . calls() . ", file: $saved\n";
Three requests to the same script Python · requests_session.py
# The commands of this session, as you would type them in a terminal; the output shows what they printed.
from session import serve

serve("php -S 127.0.0.1:8471 -t public", """
curl -s http://127.0.0.1:8471/counter.php
curl -s http://127.0.0.1:8471/counter.php
curl -s http://127.0.0.1:8471/counter.php
""")

Output

# Terminal 1: start the server
$ php -S 127.0.0.1:8471 -t public
# Terminal 2: send requests
$ curl -s http://127.0.0.1:8471/counter.php
variable: 1, static: 1, file: 1
$ curl -s http://127.0.0.1:8471/counter.php
variable: 1, static: 1, file: 2
$ curl -s http://127.0.0.1:8471/counter.php
variable: 1, static: 1, file: 3
# Terminal 1 showed this log, then Ctrl+C stopped the server:
[…] PHP 8.5.11 Development Server (http://127.0.0.1:8471) started
[…] 127.0.0.1:<port> Accepted
[…] 127.0.0.1:<port> [200]: GET /counter.php
[…] 127.0.0.1:<port> Closing
[…] 127.0.0.1:<port> Accepted
[…] 127.0.0.1:<port> [200]: GET /counter.php
[…] 127.0.0.1:<port> Closing
[…] 127.0.0.1:<port> Accepted
[…] 127.0.0.1:<port> [200]: GET /counter.php
[…] 127.0.0.1:<port> Closing

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 requests_session.py

Only the file counted. When a request ends, PHP frees everything the script created: variables, static variables and objects. The next request may even reach the same process: PHP-FPM keeps worker processes that each serve many requests in turn, and the manual’s page on persistent connections describes how a later page served by the same child process can reuse its database connection. Even then the script starts with empty memory. This is the shared-nothing: PHP's classic request model: each web request runs the script with empty memory, and PHP frees everything the script made when the request ends, so data that must last is kept in a database, a file, a cookie or a session. model, and it is why PHP sites keep whatever must last in a database, a file, a cookie or a session. It has a benefit too: a request that goes wrong cannot leave a broken value behind for the next visitor. Step through the three requests:

Interactive · message by message Three requests, one counter that lasts Step through the three requests. Watch what is left in memory after each one, and what is left in the file.
Three requests, one counter that lasts, step 8 of 8. Messages so far: curl to PHP: GET /counter.php; PHP to visits.txt: write 1; PHP to curl: 1, 1, file 1; curl to PHP: GET /counter.php; PHP to visits.txt: write 2; PHP to curl: 1, 1, file 2; PHP to curl: 1, 1, file 3.curlterminal 2PHPempty againvisits.txtholds 3GET /counter.phpwrite 11, 1, file 1GET /counter.phpwrite 21, 1, file 21, 1, file 3

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Request 1 arrives. curl asks for /counter.php. PHP starts the script from its first line with empty memory, so $visits is unset and the static $count is 0.
  2. The file is read. There is no visits.txt yet, so the script counts from 0 and writes 1 to the file.
  3. The answer. The script prints variable: 1, static: 1, file: 1, and the server sends it back.
  4. The request ends. PHP frees everything the script made, $visits and the static $count included. Only the file is left.
  5. Request 2. The second request starts from empty memory again. $visits is unset and $count is 0, but the file still holds 1.
  6. Only the file counts up. The script reads 1 from the file and writes 2, while the variable and the static variable start again at 1.
  7. The second answer. It prints variable: 1, static: 1, file: 2, and the memory is freed again when the request ends.
  8. Request 3. The third request goes the same way and prints variable: 1, static: 1, file: 3. Data that must outlive a request needs a file, a database, a cookie or a session.

Some ways of running PHP keep memory on purpose. FrankenPHP’s worker mode: A way of serving PHP in which a long-running process boots the application once and handles many requests with it, so global and static variables stay in memory from one request to the next. boots the application once and handles many requests with it, which saves the start-up work of each request. Its documentation lists what then survives from one request to the next: static variables, static properties of classes, global variables of the worker script and settings changed at run time. Code written for the classic model can then carry one user’s data into another user’s response, so it needs checking before it runs that way.

Key idea

In classic PHP every request starts with empty memory and leaves nothing behind, so whatever must last goes to a database, a file, a cookie or a session.

Checkpoint

Check: Empty memory for every request

  1. Question 1 of 2 The session sends a fourth request to /counter.php. What does it print?

    Choose one answer.

    A hint

    Which of the three counters is kept somewhere that outlives a request?

    Show the answer: Question 1 of 2

    Answer: variable: 1, static: 1, file: 4

    Only the file remembers earlier requests: it held 3, so the fourth request writes 4. The variable and the static variable are created again, from nothing, in every request.

  2. Question 2 of 2 During each request, an app keeps the signed-in user in a static variable. Under which way of running PHP can that user's name appear in the next visitor's page?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A worker mode that boots the app once and keeps it in memory

    The classic SAPIs reuse processes but not memory: every request starts empty. Only a worker mode keeps the app, and every global and static variable in it, between requests, so code written for the classic model must reset its state before it runs that way.

Four common ways to serve PHP on the web

A web server on its own sends files. To answer requests for PHP pages, something must run PHP for it, and four arrangements cover most sites:

  • PHP-FPM. PHP-FPM: The FastCGI Process Manager: a service that keeps pools of PHP worker processes and runs scripts for a web server such as Nginx or Apache httpd, which pass it each request over the FastCGI protocol., the FastCGI Process Manager, keeps pools of PHP worker processes and runs scripts for a web server such as Nginx or Apache httpd. The manual’s Apache page recommends it for every modern deployment. FPM itself must never be reachable from an untrusted network: the manual warns that a client that can send it FastCGI requests can run code.
  • The Apache module. mod_php runs PHP inside Apache httpd’s own processes. Unless PHP is built thread safe it needs Apache’s prefork mode, which limits how many requests run at once, and the manual now says not to use it for new installations.
  • The built-in server. php -S serves a folder from your own computer with one command. It handles one request at a time unless PHP_CLI_SERVER_WORKERS starts more workers, and the manual says it should not be used on a public network.
  • FrankenPHP. An application server written in Go and C that embeds PHP in the Caddy web server. It runs an existing app as it is, starting each request fresh on one of its PHP threads, or keeps the booted app in memory in worker mode.

Which one fits depends on the site. Answer the three questions about one you have in mind; the table marks what fits and gives the reason under each one that does not:

Interactive · answer and compare Which way of serving PHP fits your site? Answer the three questions about the site you have in mind. The table marks what fits, and the reason under each one that does not.
Who will open the site?
Must PHP run with an Apache httpd that the server already has?
Does the code keep data in global or static variables while it handles a request?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

The four ways to serve PHP, FrankenPHP in both of its modes, against four things a site needs
Way to serve PHP On a public site Many requests at once Empty memory per request Setup
PHP-FPM How it runs PHP: a pool of PHP processes behind a web server Good the way the manual names for modern deployments; keep FPM off untrusted networks Good a pool of worker processes takes requests from Nginx or Apache httpd Good every request starts with empty memory Mixed a web server and an FPM pool to configure
Apache module How it runs PHP: PHP inside Apache httpd, mod_php Mixed works, but the manual says not to use mod_php for new installations Poor needs Apache's prefork mode unless PHP is thread safe, which limits concurrency Good every request starts with empty memory Good one module loaded into Apache httpd
Built-in server How it runs PHP: php -S, one process Poor for development only: the manual says not to use it on a public network Poor one request at a time unless PHP_CLI_SERVER_WORKERS starts more Good every request starts with empty memory Good one command, php -S, from the project folder
FrankenPHP How it runs PHP: PHP built into the Caddy web server Good an application server that embeds PHP in the Caddy web server Good runs requests in parallel on PHP threads, about two per CPU by default Good serves an existing app as it is, each request fresh Mixed replaces the web server, so the site moves to Caddy
FrankenPHP worker mode How it runs PHP: the booted app kept in memory Good the same server, with the app booted once Good skips booting the app again for every request Poor global and static variables survive from one request to the next Poor the code must reset its own state between requests
How the answers decide
  • “Who will open the site?” Anyone, on a public network: rules out Built-in server. php -S is for development: the manual says it should not be used on a public network.
  • “Who will open the site?” Only me, on my own computer: adds a caveat to PHP-FPM and Apache module. These work on your own computer too, but need a web server set up beside PHP, where php -S needs one command.
  • “Must PHP run with an Apache httpd that the server already has?” Yes, Apache stays: adds a caveat to Apache module. It fits an Apache that is already there, but the manual says not to use mod_php for new installations: put PHP-FPM behind Apache instead.
  • “Must PHP run with an Apache httpd that the server already has?” Yes, Apache stays: adds a caveat to FrankenPHP and FrankenPHP worker mode. FrankenPHP is a web server itself, so it would sit beside that Apache or replace it.
  • “Does the code keep data in global or static variables while it handles a request?” Yes, it does: rules out FrankenPHP worker mode. Worker mode keeps global and static variables from one request to the next, so data set for one user can reach the next one.
  • “Does the code keep data in global or static variables while it handles a request?” I don't know yet: adds a caveat to FrankenPHP worker mode. Check first: in worker mode, every global and static variable the code sets is still there in the next request.

In short: for a public site, run PHP-FPM behind your web server, also when Apache httpd stays, or let FrankenPHP be the web server; keep worker mode for code that resets its own state; use php -S only on your own computer; and leave mod_php to existing setups.

Checkpoint

Check: Ways to serve PHP on the web

  1. Question 1 of 2 A new site must run on a server whose Apache httpd stays. Which way to run PHP does the PHP manual recommend?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: PHP-FPM behind Apache, connected with mod_proxy_fcgi

    For Apache httpd the manual recommends PHP-FPM, which runs PHP in its own pool of processes and lets Apache use a threaded mode. mod_php remains for existing setups only.

  2. Question 2 of 2 What does the PHP manual say about the built-in web server, php -S?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It is for development and should not be used on a public network

    php -S is a development tool: one command serves a folder on your own computer. For a public site use PHP-FPM behind a web server, or an application server such as FrankenPHP.

A PHP branch gets four years of fixes, then none

PHP 8.5.11 is a patch release of the 8.5 branch: A PHP minor version, such as 8.5, together with its patch releases (8.5.0, 8.5.1 and so on), which receive fixes until the branch reaches its end of life.. php.net supports each branch for about four years from its first release: about two years of active support, with regular releases that fix bugs and security problems, then two years of security fixes only, released when needed. After that the branch reaches its end of life: The point after which a PHP branch gets no more releases, not even security fixes. php.net lists the branches that have reached it on its end-of-life page. and gets no more releases of any kind. Walk through the branches a site may run today:

Timeline · 7 events in order PHP branches and what each still receives Walk through the branches from the oldest a site may still run to the one being tested.
  1. 7.4

    The last PHP 7 branch, end of life end of life

    7.4 reached its end of life in 2022, and its last release was 7.4.33. A site on PHP 7 has received no PHP fixes since then.

  2. 8.1

    End of life since 2025 ended end of life

    8.1 brought enumerations, readonly properties and fibers. Its security fixes ended with 2025, and 8.1.34 was its last release.

  3. 8.2

    Security fixes until 2026 ends security only

    8.2 was first released in 2022 and left active support at the end of 2024. It receives security fixes, released as needed, until the end of 2026.

  4. 8.3

    Security fixes until 2027 ends security only

    8.3 added typed class constants and json_validate() in 2023. Its active support ended with 2025, and security fixes continue until the end of 2027.

  5. 8.4

    Active support until 2026 ends active

    8.4 added property hooks and asymmetric visibility in 2024. Regular releases fix its bugs until the end of 2026, then security fixes follow until the end of 2028.

  6. 8.5

    Active support until 2027 ends active

    8.5 added the pipe operator, clone with and the URI extension in 2025. Bugs are fixed until the end of 2027 and security problems until the end of 2029. This track runs on 8.5.11.

  7. 8.6

    Being tested testing

    8.6 is in release candidates, the test builds that come before a release. php.net adds its support dates to the table once it is released.

The timeline has 7 events, from 7.4 to 8.6.

The next file holds the end dates from php.net’s table and a function that works out the status of a branch on any day. A branch older than the oldest one in the table is end of life, and a branch that is not in the table yet is reported as unknown rather than guessed:

support.php PHP · support.php
<?php

// The last day of active support and of security fixes for each supported branch, from php.net/supported-versions.php.
const SUPPORT = [
    '8.2' => ['active' => '2024-12-31', 'security' => '2026-12-31'],
    '8.3' => ['active' => '2025-12-31', 'security' => '2027-12-31'],
    '8.4' => ['active' => '2026-12-31', 'security' => '2028-12-31'],
    '8.5' => ['active' => '2027-12-31', 'security' => '2029-12-31'],
];
// Branches older than the oldest one in the table no longer receive any fixes.
const OLDEST = '8.2';

function supportStatus(string $branch, string $today): string
{
    $dates = SUPPORT[$branch] ?? null;
    if ($dates === null) {
        return version_compare($branch, OLDEST, '<') ? 'end of life' : 'unknown branch';
    }
    $day = new DateTimeImmutable($today);
    if ($day <= new DateTimeImmutable($dates['active'])) {
        return 'active support';
    }
    if ($day <= new DateTimeImmutable($dates['security'])) {
        return 'security fixes only';
    }

    return 'end of life';
}
Support status on two days PHP · support_window.php
<?php

require __DIR__ . '/support.php';

foreach (['2026-12-31', '2027-01-01'] as $today) {
    echo "On $today:\n";
    foreach (['7.4', '8.1', '8.2', '8.3', '8.4', '8.5', '8.6'] as $branch) {
        printf("  PHP %s  %s\n", $branch, supportStatus($branch, $today));
    }
}

Output

On 2026-12-31:
  PHP 7.4  end of life
  PHP 8.1  end of life
  PHP 8.2  security fixes only
  PHP 8.3  security fixes only
  PHP 8.4  active support
  PHP 8.5  active support
  PHP 8.6  unknown branch
On 2027-01-01:
  PHP 7.4  end of life
  PHP 8.1  end of life
  PHP 8.2  end of life
  PHP 8.3  security fixes only
  PHP 8.4  security fixes only
  PHP 8.5  active support
  PHP 8.6  unknown branch

Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php support_window.php

The last day of 2026 is the last day of 8.2’s security fixes and of 8.4’s active support, and one day later both change: 8.2 reaches its end of life and 8.4 receives security fixes only. 8.6 is still being tested in release candidates, so the program does not know it yet; its dates join the table when php.net publishes them.

How many sites does that affect? W3Techs counts the PHP versions that websites report. The next program combines its shares with the same table:

PHP websites by support status PHP · branch_shares.php
<?php

require __DIR__ . '/support.php';

// Shares from W3Techs' survey of websites: each major version as a percentage of the websites that use PHP, and
// each 8.x branch as a percentage of the websites that use PHP 8.
const MAJOR = ['8' => 65.1, '7' => 27.1, '5' => 7.7, '4' => 0.1];
const PHP8 = ['8.0' => 5.9, '8.1' => 15.3, '8.2' => 27.9, '8.3' => 34.8, '8.4' => 12.0, '8.5' => 4.2];

// Every branch as a percentage of all PHP websites.
$shares = [];
foreach (PHP8 as $branch => $percentOfPhp8) {
    $shares[$branch] = MAJOR['8'] * $percentOfPhp8 / 100;
}
$shares += ['7' => MAJOR['7'], '5' => MAJOR['5'], '4' => MAJOR['4']];

$days = [
    '2026-12-31' => 'PHP websites by what their branch receives on 2026-12-31:',
    '2027-01-01' => 'The same websites a day later, if none of them upgrades:',
];
foreach ($days as $today => $heading) {
    $byStatus = ['active support' => 0.0, 'security fixes only' => 0.0, 'end of life' => 0.0];
    foreach ($shares as $branch => $share) {
        $byStatus[supportStatus((string) $branch, $today)] += $share;
    }
    echo $heading, "\n";
    foreach ($byStatus as $status => $share) {
        printf("  %-20s %5.1f %%\n", $status, $share);
    }
}

Output

PHP websites by what their branch receives on 2026-12-31:
  active support        10.5 %
  security fixes only   40.8 %
  end of life           48.7 %
The same websites a day later, if none of them upgrades:
  active support         2.7 %
  security fixes only   30.5 %
  end of life           66.9 %

Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php branch_shares.php

By those shares, 48.7 % of PHP websites already run a branch without security fixes. On the first day of 2027 every 8.2 site joins them, 66.9 % in all, unless they upgrade before then.

To find the branch of a site you maintain, ask the PHP that serves it. A temporary script that prints PHP_VERSION, requested through the site and deleted straight after, answers that, and so does the X-Powered-By header that the session above showed, as long as the expose_php setting is on.

Trusting php -v on the server

php -v in a server’s terminal reports the command line’s PHP. The website can be served by another build, such as an older PHP-FPM that a hosting panel or a package left behind. Check the version through the website itself before you decide anything about its upgrade.

Checkpoint

Check: What a branch still receives

  1. Question 1 of 2 By php.net's table, what is the status of PHP 8.2 on the first day of 2027?

    Choose one answer.

    A hint

    support_window.php printed the status of every branch on that day.

    Show the answer: Question 1 of 2

    Answer: end of life

    A branch's security fixes end on the date in php.net's table, and the next day it is end of life. For 8.2 that day is the first day of 2027, as the second half of the program's output shows.

  2. Question 2 of 2 php -v in the server's terminal reports PHP 8.4. Which check tells you the branch that serves the website itself?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Ask the website, with a temporary script that prints PHP_VERSION or by reading its X-Powered-By header

    The command line and the web server can use different PHP builds. Check the version the website reports, from a script that prints PHP_VERSION (delete it straight after) or from the X-Powered-By header while expose_php is on.

Every recorded run names the PHP behind it

The programs in this track show what they printed on PHP 8.5.11, and the command to run each one on your own computer. Read the first program’s output again: 8.5.11 is the version, in the branch 8.5; Darwin is the operating system family of macOS, one of the values the manual lists for PHP_OS_FAMILY, with Linux, Windows, BSD, Solaris and Unknown; integers are 64 bits wide; and cli says the php command ran it.

The size of an integer is a property of the build. The manual puts the largest integer at about two billion on 32-bit builds and about 9 × 10¹⁸ on 64-bit ones, and a number beyond the largest integer becomes a float. This program shows where that line falls on the PHP that recorded this track’s runs:

What the size of an integer changes PHP · int_limits.php
<?php

// What the size of an integer changes. A 64-bit build prints what this page shows.
echo 'PHP_INT_SIZE  ', PHP_INT_SIZE, " bytes\n";
echo 'PHP_INT_MAX   ', PHP_INT_MAX, "\n";
var_dump(2_147_483_647 + 1);
var_dump(3_000_000_000);
var_dump(PHP_INT_MAX + 1);

Output

PHP_INT_SIZE  8 bytes
PHP_INT_MAX   9223372036854775807
int(2147483648)
int(3000000000)
float(9.223372036854776E+18)

Recorded with PHP 8.5.11 (cli) (NTS) on macOS 26 arm64. To run it yourself: php int_limits.php

On this 64-bit build 2,147,483,648 and 3,000,000,000 are still integers, and only PHP_INT_MAX + 1 became a float. A 32-bit build, whose PHP_INT_MAX is 2,147,483,647, prints the first two as float(2147483648) and float(3000000000). So when your own run of an example differs from the page, compare PHP_VERSION, PHP_INT_SIZE and PHP_SAPI before anything else.

The runs also use PHP’s built-in settings, with no php.ini file of their own. The next lesson shows how to check which settings file your own PHP reads.

Checkpoint

Check: Reading a recorded run

  1. One question A run of int_limits.php on another build prints PHP_INT_SIZE 4 bytes. What does var_dump(3_000_000_000); print there?

    Choose one answer.

    Show the answer: One question

    Answer: float(3000000000)

    PHP_INT_SIZE 4 means 32-bit integers, so PHP_INT_MAX is 2,147,483,647 and 3,000,000,000 is a float. Compare PHP_INT_SIZE first when your output differs from a recorded run.

Practise: PHP and where it runs

The practice below covers SAPIs, request memory, the ways to serve PHP, support dates and reading a run. It opens where your answers are weakest and gives the reasoning behind every answer:

Practice that adapts to you

Practise this lesson

17 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 17 What is a SAPI in PHP?

    Choose one answer.

    Show the answer: Question 1 of 17

    Answer: The part of PHP that connects the engine to the program that starts it

    SAPI stands for Server API. The php command, PHP-FPM, the Apache module and the built-in server are SAPIs, and PHP_SAPI says which one is running a script.

  2. Question 2 of 17 A cron job runs php /var/www/app/cleanup.php every night on a server whose website uses PHP-FPM. Which SAPI runs the cleanup script?

    Choose one answer.

    Show the answer: Question 2 of 17

    Answer: cli

    A script started by the php command, from a terminal, cron or a deploy tool, runs under the CLI SAPI. The site's SAPI only matters for web requests.

  3. Question 3 of 17 A script must print plain text on the command line and HTML under a web server. Which test does the PHP manual recommend for telling the two apart?

    Choose one answer.

    Show the answer: Question 3 of 17

    Answer: PHP_SAPI === 'cli', or php_sapi_name() === 'cli'

    The manual's page on the command line recommends checking the SAPI name. PHP_SAPI holds the same value as php_sapi_name(): cli for the php command.

  4. Question 4 of 17 Under PHP-FPM, what is left of one request's variables when the next request starts?

    Choose one answer.

    Show the answer: Question 4 of 17

    Answer: Nothing

    That is the shared-nothing model: requests do not share memory. Data that must last is written to a database, a file, a cookie or a session.

  5. Question 5 of 17 A shop keeps the visitor's cart in a global array, $cart, filled when an item is added. On the next page the cart is empty. Why, and what is the fix?

    Choose one answer.

    Show the answer: Question 5 of 17

    Answer: Each request starts with empty memory, so the cart must be stored in a session, a cookie or the database

    The cart has to be written somewhere that outlives the request: PHP's sessions, a cookie, or a table in the database. Each new request then reads it back from there.

  6. Question 6 of 17 PHP-FPM keeps each worker process alive for many requests. Can a static variable carry a value from one request to the next request in the same worker?

    Choose one answer.

    Show the answer: Question 6 of 17

    Answer: No, PHP frees the request's memory when it ends, even when the same worker takes the next one

    A reused process is not reused memory. PHP-FPM's workers save the cost of starting a process; each request still begins with nothing from the last one.

  7. Question 7 of 17 What does PHP-FPM do?

    Choose one answer.

    Show the answer: Question 7 of 17

    Answer: It keeps pools of PHP worker processes and runs scripts for a web server over FastCGI

    FPM stands for FastCGI Process Manager. It is the usual way a web server runs PHP today, and its pools can run with different users and settings.

  8. Question 8 of 17 Why must PHP-FPM's FastCGI port never be reachable from the internet?

    Choose one answer.

    Show the answer: Question 8 of 17

    Answer: A client that can talk FastCGI to it controls the request's settings and can run code

    PHP-FPM trusts whoever sends it FastCGI requests. Limit it with listen and listen.allowed_clients so that only the web server can reach it.

  9. Question 9 of 17 A large framework spends most of each request booting itself. Which arrangement saves that work, and what does it ask of the code?

    Choose one answer.

    Show the answer: Question 9 of 17

    Answer: FrankenPHP's worker mode; the code must reset its global and static state between requests

    Only a worker mode keeps the booted app between requests. The saving is real, and so is the risk: state that the classic model threw away now leaks from one request into the next.

  10. Question 10 of 17 In which order does php.net support a PHP branch?

    Choose one answer.

    Show the answer: Question 10 of 17

    Answer: Active support, then security fixes only, then end of life

    php.net's supported-versions page gives each branch an end of active support and an end of security support. After the second date the branch is end of life.

  11. Question 11 of 17 Which branches are in active support on the last day of 2026, by php.net's table?

    Choose every answer that is right.

    A hint

    Look at the first half of the output of support_window.php.

    Show the answer: Question 11 of 17

    Answer:

    • 8.4
    • 8.5

    On the last day of 2026, 8.4 and 8.5 are actively supported and 8.2 and 8.3 receive security fixes only. A day later 8.4 moves to security fixes only and 8.2 reaches its end of life.

  12. Question 12 of 17 By the W3Techs shares in branch_shares.php, what percentage of PHP websites run an end-of-life branch on the first day of 2027 if none of them upgrades?

    Type a number.

    A hint

    It is the last line of the program's output.

    Show the answer: Question 12 of 17

    Answer: 66.9 % (anything from 66.85 to 66.95 counts)

    The program adds up the shares of every branch whose status is end of life on that day: PHP 7, 5 and 4, 8.0, 8.1 and now 8.2, which together make 66.9 % of PHP websites.

  13. Question 13 of 17 A client's shop runs PHP 8.1 and works well. They ask whether it can stay on 8.1 for another year. What do you answer?

    Choose one answer.

    Show the answer: Question 13 of 17

    Answer: No; 8.1 has had no security fixes since 2025 ended, so plan an upgrade to 8.4 or 8.5 and test the shop on it first

    End of life means no more releases at all. Move to a branch with years of support left, 8.4 or 8.5 today, after testing the site on it.

  14. Question 14 of 17 The recorded output of version_check.php ends with SAPI cli. What does that tell you?

    Choose one answer.

    Show the answer: Question 14 of 17

    Answer: The run came from the php command, not from a web server

    Each line of the program names one fact a run depends on: the version, its branch, the operating system family, the integer size and the SAPI that started PHP.

  15. Question 15 of 17 What does int_limits.php print on the 64-bit PHP 8.5.11 that recorded this track's runs?

    What does this program print? Choose one answer.

    <?php
    
    // What the size of an integer changes. A 64-bit build prints what this page shows.
    echo 'PHP_INT_SIZE  ', PHP_INT_SIZE, " bytes\n";
    echo 'PHP_INT_MAX   ', PHP_INT_MAX, "\n";
    var_dump(2_147_483_647 + 1);
    var_dump(3_000_000_000);
    var_dump(PHP_INT_MAX + 1);
    Show the answer: Question 15 of 17

    Answer: it prints

    PHP_INT_SIZE  8 bytes
    PHP_INT_MAX   9223372036854775807
    int(2147483648)
    int(3000000000)
    float(9.223372036854776E+18)

    On a 64-bit build PHP_INT_MAX is 9,223,372,036,854,775,807. Both smaller numbers are ints, and PHP_INT_MAX + 1 is the one value that crosses the limit, so PHP gives it as a float, 9.223372036854776E+18.

  16. Question 16 of 17 What is PHP_INT_MAX on a build whose PHP_INT_SIZE is 4?

    Choose one answer.

    Show the answer: Question 16 of 17

    Answer: 2147483647

    PHP_INT_SIZE is in bytes. Four bytes give a largest integer of about two billion, eight bytes about 9.2 × 10¹⁸.

  17. Question 17 of 17 Your own run of a lesson's example prints float(2147483648) where the page shows int(2147483648). Which constant do you compare first?

    Choose one answer.

    Show the answer: Question 17 of 17

    Answer: PHP_INT_SIZE

    2,147,483,648 is 2³¹, one more than PHP_INT_MAX on a 32-bit build, where it becomes a float. Check PHP_INT_SIZE before anything else when integer output differs.

Interview questions

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher · Warm-up

    What is a SAPI in PHP, and which SAPI runs a script started with php script.php?

    A SAPI, short for Server API, is the part of PHP that connects the engine to the program that starts it and gives the script its input. The engine that compiles and runs code is the same everywhere; the SAPI differs. The php command is the CLI SAPI, so php script.php runs under cli, with the command-line arguments in $argv and no web request. A web server reaches PHP through another SAPI: fpm-fcgi under PHP-FPM, apache2handler under the Apache module, cli-server under the built-in server started with php -S. A script can read the name from the PHP_SAPI constant or php_sapi_name(), and the manual recommends that check when code must behave differently on the command line and on the web.

  2. Fresher to senior

    A client's online shop runs PHP 8.1 and works without problems. They would like to stay on it for another year to save the cost of an upgrade. What do you tell them, which branch do you recommend, and how would you plan the move?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    To cut response times, an app moved from PHP-FPM to an application server's worker mode. Since then a few users report seeing another user's name in the page header, but nobody can reproduce it on demand. What do you suspect, how do you confirm it, and how do you fix it?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • The same PHP engine runs every script; the SAPI that starts it, cli for the php command or cli-server, fpm-fcgi and apache2handler for web servers, decides what input the script gets.
  • Under PHP-FPM, the Apache module and the built-in server, each request starts with empty memory, so data that must last goes to a database, a file, a cookie or a session; a worker mode keeps memory between requests and needs code that resets its own state.
  • A public site runs PHP-FPM behind its web server or uses FrankenPHP; php -S is for development only, and the manual advises against mod_php for new installations.
  • A branch gets about two years of active support and two of security fixes only; 8.1 and older get nothing, and by W3Techs’ shares about half of PHP websites still run one of them.
  • Every recorded run in this track is PHP 8.5.11 with 64-bit integers under the CLI SAPI, so PHP_VERSION, PHP_INT_SIZE and PHP_SAPI are the first things to compare when your own run differs.

Terms in this lesson

branch
A PHP minor version, such as 8.5, together with its patch releases (8.5.0, 8.5.1 and so on), which receive fixes until the branch reaches its end of life.
end of life
The point after which a PHP branch gets no more releases, not even security fixes. php.net lists the branches that have reached it on its end-of-life page.
PHP-FPM
The FastCGI Process Manager: a service that keeps pools of PHP worker processes and runs scripts for a web server such as Nginx or Apache httpd, which pass it each request over the FastCGI protocol.
SAPI
Server API: the part of PHP that connects its engine to the program that starts it, such as the php command (cli), PHP-FPM (fpm-fcgi), the Apache module or the built-in web server (cli-server). PHP_SAPI holds its name.
shared-nothing
PHP's classic request model: each web request runs the script with empty memory, and PHP frees everything the script made when the request ends, so data that must last is kept in a database, a file, a cookie or a session.
worker mode
A way of serving PHP in which a long-running process boots the application once and handles many requests with it, so global and static variables stay in memory from one request to the next.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.