# The Request Lifecycle: Startup, Execution, Shutdown

> The PHP request lifecycle explained: MINIT, RINIT, RSHUTDOWN, MSHUTDOWN, worker reuse and why CVE-2026-109619 targets exactly this point.

Source: https://phpsos.de/en/php/request-lebenszyklus/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** The PHP request lifecycle has two levels. Once per process, PHP loads its extensions (phase MINIT) and tears them down again at the end (MSHUTDOWN). For each individual request there is a separate startup and clean-up phase (RINIT and RSHUTDOWN). In web operation, a worker handles many requests one after another, so PHP has to reliably reset all request state between two requests. CVE-2026-109619, the second PHP2Shell vulnerability, lay precisely at this boundary.

## Two levels: the process and the request

For non-technical readers, an image helps: a PHP worker is like a counter at a government office. The counter is set up once in the morning, with forms, stamps and a computer. Then many visitors come one after another. After each visitor, the desk must be empty so that the next one does not see someone else's papers.

PHP works the same way. When a process starts, it loads extensions and settings that are the same for all requests. Each request then gets its own memory, its own variables and its own request data such as `$_GET`, `$_POST` or `$_FILES`. At the end of the request, PHP releases this memory. The PHP Internals Book sums it up briefly: PHP forgets the request memory at the end of every request.

## The phases of the PHP request lifecycle at a glance

The names come from the inside of PHP; every extension can hook into these phases.

| Phase | When it runs | How often | Typical task |
|---|---|---|---|
| MINIT (Module Init) | when the process starts | once per process | register extensions, create ini entries, persistent data |
| GINIT | when the globals are created | per process, with threads per thread | prepare an extension's global variables |
| RINIT (Request Init) | at the start of each request | per request | prepare request memory and request state |
| Execution | in between | per request | your PHP code runs |
| RSHUTDOWN | at the end of each request | per request | release request resources |
| PRSHUTDOWN | after RSHUTDOWN | per request, rarely used | final clean-up work |
| MSHUTDOWN | when the process ends | once per process | release persistent data |

An important sentence from the PHP Internals Book for extension developers: globals are not automatically cleared after every request. Whatever has to be fresh for each request belongs in RINIT to be reset. This rule shows why the boundary between two requests is a security-critical point.

At the end of a request the order is also fixed: first the shutdown functions you registered with `register_shutdown_function()` run, then destructors, then the output buffer is flushed. Only after that does RSHUTDOWN follow.

## Why workers are reused

On the command line, a process handles exactly one request and ends. In web operation that would be too slow. PHP-FPM, Apache with mod_php, LiteSpeed and FrankenPHP therefore keep workers ready that serve many requests in a row. MINIT then runs once, RINIT and RSHUTDOWN thousands of times. How FPM controls its workers is explained in [PHP-FPM explained](https://phpsos.de/en/php/php-fpm-erklaert/).

In PHP-FPM, `pm.max_requests` determines after how many requests a worker restarts. The default is `0`, that is, unlimited. The manual names the setting as a remedy against memory leaks in third-party libraries:

```ini
; FPM pool: restart workers after 500 requests (against memory leaks)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500
```

FrankenPHP's worker mode goes further still: there the whole application boots once and stays in memory. Application state then survives at the PHP level as well. The example shows the difference:

```php
<?php
function anfrageZaehler(): int
{
    static $zaehler = 0;   // in classic FPM operation 0 again for each request
    return ++$zaehler;     // in worker mode the value keeps counting across requests
}
echo anfrageZaehler();
```

In classic operation, the call always outputs 1, because PHP discards the request memory. In worker mode the value grows for as long as the worker lives. The FrankenPHP documentation therefore advises deliberately resetting request-specific state between requests.

## Where CVE-2026-109619 comes in

CVE-2026-109619 does not concern your application code but PHP itself. According to php.net, internal state belonging to a request could survive the freeing of its memory. A later request in the same worker could use it before initialisation reset it. In the image: a slip of paper was left on the counter desk that should already have been cleared away. We deliberately do not publish details of exploitation; the classification is found under [CVE-2026-109619](https://phpsos.de/en/php2shell/cve-2026-109619/).

The php.net interim measures for this vulnerability target confirmed paths; they are not complete protection:

1. `session.upload_progress.enabled=0` in php.ini, the FPM pool or the server configuration, then restart all workers; not via `ini_set()` or `.user.ini`.
2. On PHP 8.5, additionally `fatal_error_backtraces=0` and a restart.
3. Remove invalid or outdated settings per request that produce messages before initialisation. According to php.net, merely hiding the messages does not help.
4. Fix externally triggerable fatal errors and timeouts in the application, including in callbacks and in upload and import processing.

The actual remedy is the update to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35. Because the flaw sits in the running worker, it takes effect only after a restart of all processes; the steps are described under [Restarting PHP-FPM and all workers](https://phpsos.de/en/php2shell/php-fpm-neustart/).

> **Tip:**
> After the restart, use `ps -eo pid,lstart,cmd | grep php` to check that all PHP processes were started after the time of the update.

## What the lifecycle means for operations and development

Clear rules follow from the model. For admins: an update is effective only when no process with old code is running any more. Every operating mode with long-lived workers needs a documented restart. For developers: code that runs in worker modes must not leave request-specific state lying around in static variables or singletons, such as user data or permissions. For both: keep logs from 20 October 2026 onwards so that it can later be assessed whether anything happened before the patch.

## How does PHP SOS help?

With the [PHP patch service](https://phpsos.de/en/leistungen/php-patch-service/), we put the model into practice:

- **Determine the operating mode:** We establish whether PHP-FPM, mod_php, LiteSpeed, FrankenPHP or php-cgi is running and how workers are managed.
- **Update and restart:** We install the corrected version and restart every worker in a controlled way.
- **Process check:** Using start times and versions, we demonstrate that no old process is left.
- **Check worker modes:** For long-running applications, we look at whether request-specific state is reset cleanly.


## Frequently asked questions

**Does PHP start a new process for every request?**  
On the command line, yes; in web operation, usually not. PHP-FPM, Apache with mod_php, LiteSpeed or FrankenPHP let one process handle many requests one after another. Between requests, PHP cleans up the request state.

**What is the difference between MINIT and RINIT?**  
MINIT runs once when the PHP process starts and sets up persistent structures. RINIT runs at the start of each individual request and prepares what only that request needs.

**Does pm.max_requests help against CVE-2026-109619?**  
php.net does not name more frequent worker restarts as an interim measure. The setting only limits how many requests a worker handles before it restarts. What helps against the vulnerability is the update followed by a restart of all workers.

**What is the worker mode of FrankenPHP?**  
In worker mode, the application boots once and stays in memory. This saves time, but static variables, globals and caches are retained between requests. According to the FrankenPHP documentation, request-specific state must therefore be reset deliberately.

## Sources

- [php.net: Security announcement of 21 October 2026](https://www.php.net/)
- [PHP Manual: FPM configuration](https://www.php.net/manual/en/install.fpm.configuration.php)
- [PHP Internals Book: The lifecycle of PHP](https://www.phpinternalsbook.com/)
