Actuele situatie: PHP2Shell. Twee kritieke PHP-kwetsbaarheden (CVSS 9.2) maken schadelijke code zonder inloggen mogelijk. Patches sinds 21-10-2026.Wat u nu moet doen →

De levenscyclus van een verzoek: startup, uitvoering, shutdown

Hoe PHP processen start, verzoeken afhandelt en er tussendoor opruimt. Met de fasen MINIT tot MSHUTDOWN en het verband met CVE-2026-109619.

Stand 5 min. leestijdPHP-kennis

Kort gezegd: De PHP request lifecycle heeft twee niveaus. Eenmaal per proces laadt PHP zijn extensies (fase MINIT) en breekt ze aan het einde weer af (MSHUTDOWN). Voor elk afzonderlijk verzoek is er een eigen start- en opruimfase (RINIT en RSHUTDOWN). Bij webgebruik handelt een worker veel verzoeken achter elkaar af, daarom moet PHP tussen twee verzoeken alle verzoekstatus betrouwbaar terugzetten. Precies op deze grens lag CVE-2026-109619, het tweede PHP2Shell-lek.

Twee niveaus: het proces en het verzoek

Voor niet-technici helpt een beeld: een PHP-worker is als een loket bij een overheidsinstantie. Het loket wordt 's ochtends eenmaal ingericht, met formulieren, stempels en computer. Daarna komen achter elkaar veel bezoekers. Na elke bezoeker moet de tafel leeg zijn, zodat de volgende geen vreemde stukken ziet.

PHP werkt net zo. Bij de start van een proces laadt het extensies en instellingen die voor alle verzoeken gelijk zijn. Elk verzoek krijgt dan eigen geheugen, eigen variabelen en eigen verzoekgegevens zoals $_GET, $_POST of $_FILES. Aan het einde van het verzoek geeft PHP dit geheugen vrij. Het PHP Internals Book vat het kort samen: PHP vergeet het verzoekgeheugen aan het einde van elk verzoek.

De fasen van de PHP request lifecycle in een overzicht

De namen komen uit het binnenste van PHP; elke extensie kan zich in deze fasen inhaken.

Fase Wanneer ze draait Hoe vaak Typische taak
MINIT (Module Init) bij de start van het proces eenmaal per proces extensies registreren, ini-items aanmaken, blijvende gegevens
GINIT bij het aanmaken van de globals per proces, bij threads per thread globale variabelen van een extensie voorbereiden
RINIT (Request Init) aan het begin van elk verzoek per verzoek verzoekgeheugen en verzoekstatus voorbereiden
Uitvoering daartussen per verzoek uw PHP-code draait
RSHUTDOWN aan het einde van elk verzoek per verzoek verzoekbronnen vrijgeven
PRSHUTDOWN na RSHUTDOWN per verzoek, zelden gebruikt laatste opruimwerk
MSHUTDOWN bij het beëindigen van het proces eenmaal per proces blijvende gegevens vrijgeven

Een belangrijke zin uit het PHP Internals Book voor extensieontwikkelaars: globals worden niet na elk verzoek automatisch geleegd. Wat per verzoek vers moet zijn, hoort in RINIT te worden teruggezet. Deze regel laat zien waarom de grens tussen twee verzoeken een beveiligingskritieke plek is.

Aan het einde van een verzoek ligt de volgorde eveneens vast: eerst draaien shutdown-functies die u met register_shutdown_function() hebt geregistreerd, dan destructors, dan wordt de uitvoerbuffer geleegd. Pas daarna volgt RSHUTDOWN.

Waarom workers worden hergebruikt

Op de opdrachtregel handelt een proces precies één verzoek af en eindigt. Bij webgebruik zou dat te traag zijn. PHP-FPM, Apache met mod_php, LiteSpeed en FrankenPHP houden daarom workers paraat die veel verzoeken achter elkaar bedienen. MINIT draait dan eenmaal, RINIT en RSHUTDOWN duizenden keren. Hoe FPM zijn workers aanstuurt, legt PHP-FPM uitgelegd uit.

In PHP-FPM bepaalt pm.max_requests na hoeveel verzoeken een worker opnieuw start. De standaard is 0, dus onbeperkt. De handleiding noemt de instelling als middel tegen geheugenlekken in bibliotheken van derden:

; FPM-pool: worker na 500 verzoeken opnieuw starten (tegen geheugenlekken)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500

Nog verder gaat de worker-modus van FrankenPHP: daar start de hele toepassing eenmaal op en blijft in het geheugen. Toepassingsstatus overleeft dan ook op PHP-niveau. Het voorbeeld toont het verschil:

<?php
function anfrageZaehler(): int
{
    static $zaehler = 0;   // in klassiek FPM-bedrijf per verzoek weer 0
    return ++$zaehler;     // in de worker-modus telt de waarde over verzoeken heen door
}
echo anfrageZaehler();

In klassiek bedrijf geeft de aanroep altijd 1 weer, omdat PHP het verzoekgeheugen verwerpt. In de worker-modus groeit de waarde zolang de worker leeft. De FrankenPHP-documentatie raadt daarom aan verzoekgebonden status tussen verzoeken bewust terug te zetten.

Waar CVE-2026-109619 aangrijpt

CVE-2026-109619 betreft niet uw toepassingscode, maar PHP zelf. Volgens php.net kon bij een verzoek behorende interne toestand het vrijgeven van zijn geheugen overleven. Een later verzoek in dezelfde worker kon hem gebruiken voordat de initialisatie hem terugzette. In het beeld: op de loket bleef een briefje liggen dat eigenlijk al was opgeruimd. Details over het misbruik publiceren wij bewust niet; de duiding staat onder CVE-2026-109619.

De overgangsmaatregelen van php.net voor dit lek grijpen aan op bevestigde routes, ze zijn geen volledige bescherming:

  1. session.upload_progress.enabled=0 in php.ini, FPM-pool of serverconfiguratie, daarna alle workers opnieuw starten; niet via ini_set() of .user.ini.
  2. Op PHP 8.5 bovendien fatal_error_backtraces=0 en herstart.
  3. Ongeldige of verouderde instellingen per verzoek verwijderen die vóór de initialisatie meldingen veroorzaken. Meldingen alleen verbergen helpt volgens php.net niet.
  4. Van buitenaf uit te lokken fatale fouten en time-outs in de toepassing verhelpen, ook in callbacks, upload- en importverwerking.

De eigenlijke oplossing is de update naar PHP 8.5.12, 8.4.27, 8.3.36 of 8.2.35. Omdat de fout in de lopende worker zit, werkt ze pas na een herstart van alle processen; de stappen staan onder PHP-FPM en alle workers opnieuw starten.

Tip

Controleer na de herstart met ps -eo pid,lstart,cmd | grep php of alle PHP-processen na het tijdstip van de update zijn gestart.

Wat de levenscyclus betekent voor beheer en ontwikkeling

Uit het model volgen duidelijke regels. Voor beheerders: een update is pas werkzaam als er geen proces met oude code meer draait. Elke bedrijfsmodus met langlevende workers heeft een gedocumenteerde herstart nodig. Voor ontwikkelaars: code die in worker-modi draait, mag geen verzoekgebonden status in statische variabelen of singletons laten liggen, zoals gebruikersgegevens of rechten. Voor beiden geldt: bewaar logs vanaf 20 oktober 2026, om later te kunnen beoordelen of er vóór de patch iets is gebeurd.

Hoe helpt PHP SOS concreet?

Met de PHP-patchservice zetten wij het model in de praktijk om:

  • Bedrijfsmodus bepalen: Wij stellen vast of PHP-FPM, mod_php, LiteSpeed, FrankenPHP of php-cgi draait en hoe workers worden beheerd.
  • Update en herstart: Wij voeren de gecorrigeerde versie door en starten elke worker gecontroleerd opnieuw.
  • Procescontrole: Wij tonen aan de hand van starttijden en versies aan dat er geen oud proces is overgebleven.
  • Worker-modi controleren: Bij langlevende toepassingen bekijken wij of verzoekgebonden status netjes wordt teruggezet.

Veelgestelde vragen

Start PHP voor elk verzoek een nieuw proces?

Op de opdrachtregel wel, bij webgebruik meestal niet. PHP-FPM, Apache met mod_php, LiteSpeed of FrankenPHP laten een proces veel verzoeken achter elkaar afhandelen. Tussen de verzoeken ruimt PHP de verzoekstatus op.

Wat is het verschil tussen MINIT en RINIT?

MINIT draait eenmaal, wanneer het PHP-proces start, en legt blijvende structuren aan. RINIT draait aan het begin van elk afzonderlijk verzoek en bereidt voor wat alleen dit verzoek nodig heeft.

Helpt pm.max_requests tegen CVE-2026-109619?

php.net noemt vaker herstarten van workers niet als overgangsmaatregel. De instelling begrenst alleen hoeveel verzoeken een worker afhandelt voordat hij opnieuw start. Tegen het lek helpt de update met aansluitend een herstart van alle workers.

Wat is de worker-modus van FrankenPHP?

In de worker-modus start de toepassing eenmaal op en blijft in het geheugen. Dat bespaart tijd, maar statische variabelen, globals en caches blijven tussen verzoeken behouden. Volgens de FrankenPHP-documentatie moet verzoekgebonden status daarom bewust worden teruggezet.

Bronnen

Direct contact

Elk uur telt. Schrijf ons nu.

Beschrijf kort het systeem, de PHP-versie en wat u opmerkt. Wij nemen contact op, brengen de situatie in kaart en werken op afstand tot het incident is afgerond en gedocumenteerd.

Directe hulp