Aktuální situace: PHP2Shell. Dvě kritické zranitelnosti PHP (CVSS 9.2) umožňují spustit škodlivý kód bez přihlášení. Záplaty od 21. 10. 2026.Co nyní udělat →

Životní cyklus požadavku: start, provedení, shutdown

Jak PHP spouští procesy, vyřizuje požadavky a mezitím uklízí. S fázemi MINIT až MSHUTDOWN a souvislostí s CVE-2026-109619.

Stav 5 min čteníZnalosti o PHP

Stručně: Životní cyklus požadavku PHP má dvě úrovně. Jednou za proces PHP načte svá rozšíření (fáze MINIT) a nakonec je zase odbourá (MSHUTDOWN). Pro každý jednotlivý požadavek existuje vlastní fáze startu a úklidu (RINIT a RSHUTDOWN). Ve webovém provozu vyřizuje worker mnoho požadavků po sobě, proto musí PHP mezi dvěma požadavky spolehlivě vynulovat veškerý stav požadavku. Právě na této hranici ležela CVE-2026-109619, druhá zranitelnost PHP2Shell.

Dvě úrovně: proces a požadavek

Pro netechniky pomůže přirovnání: worker PHP je jako přepážka na úřadě. Přepážka se ráno jednou zařídí, s formuláři, razítky a počítačem. Poté přicházejí postupně mnozí návštěvníci. Po každém návštěvníkovi musí být stůl prázdný, aby další neviděl cizí podklady.

PHP funguje stejně. Při startu procesu načte rozšíření a nastavení, která jsou pro všechny požadavky stejná. Každý požadavek pak dostane vlastní paměť, vlastní proměnné a vlastní data požadavku, jako jsou $_GET, $_POST nebo $_FILES. Na konci požadavku PHP tuto paměť uvolní. PHP Internals Book to shrnuje stručně: PHP na konci každého požadavku zapomene paměť požadavku.

Fáze životního cyklu požadavku PHP v přehledu

Názvy pocházejí z nitra PHP; každé rozšíření se může do těchto fází zapojit.

Fáze Kdy probíhá Jak často Typický úkol
MINIT (Module Init) při startu procesu jednou na proces registrovat rozšíření, zřídit položky ini, trvalá data
GINIT při vytváření globálních proměnných na proces, u vláken na vlákno připravit globální proměnné rozšíření
RINIT (Request Init) na začátku každého požadavku na požadavek připravit paměť a stav požadavku
Provedení mezitím na požadavek běží váš kód PHP
RSHUTDOWN na konci každého požadavku na požadavek uvolnit zdroje požadavku
PRSHUTDOWN po RSHUTDOWN na požadavek, používá se zřídka poslední úklidové práce
MSHUTDOWN při ukončení procesu jednou na proces uvolnit trvalá data

Důležitá věta z PHP Internals Book pro vývojáře rozšíření: globální proměnné se po každém požadavku automaticky nevyprazdňují. Co musí být pro každý požadavek čerstvé, patří do RINIT k resetování. Toto pravidlo ukazuje, proč je hranice mezi dvěma požadavky bezpečnostně kritickým místem.

Na konci požadavku je pořadí rovněž pevně dané: nejdřív proběhnou shutdown funkce, které jste zaregistrovali pomocí register_shutdown_function(), poté destruktory, poté se vyprázdní výstupní buffer. Teprve potom následuje RSHUTDOWN.

Proč se workery používají opakovaně

Na příkazové řádce vyřídí proces právě jeden požadavek a skončí. Ve webovém provozu by to bylo příliš pomalé. PHP-FPM, Apache s mod_php, LiteSpeed a FrankenPHP proto drží workery, které obsluhují mnoho požadavků za sebou. MINIT pak proběhne jednou, RINIT a RSHUTDOWN tisíckrát. Jak FPM své workery řídí, vysvětluje stránka PHP-FPM vysvětleno.

V PHP-FPM určuje pm.max_requests, po kolika požadavcích se worker restartuje. Výchozí hodnota je 0, tedy neomezeně. Příručka toto nastavení uvádí jako prostředek proti únikům paměti v knihovnách třetích stran:

; pool FPM: restartovat worker po 500 požadavcích (proti únikům paměti)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500

Ještě dál jde režim worker u FrankenPHP: tam se celá aplikace spustí jednou a zůstává v paměti. Stav aplikace pak přežívá i na úrovni PHP. Příklad ukazuje rozdíl:

<?php
function anfrageZaehler(): int
{
    static $zaehler = 0;   // v klasickém provozu FPM je na každý požadavek opět 0
    return ++$zaehler;     // v režimu worker hodnota počítá dál napříč požadavky
}
echo anfrageZaehler();

V klasickém provozu vypíše volání vždy 1, protože PHP paměť požadavku zahodí. V režimu worker hodnota roste, dokud worker žije. Dokumentace FrankenPHP proto radí stav vázaný na požadavek mezi požadavky vědomě resetovat.

Kde CVE-2026-109619 míří

CVE-2026-109619 se netýká vašeho aplikačního kódu, ale samotného PHP. Podle php.net mohl interní stav patřící k požadavku přežít uvolnění své paměti. Pozdější požadavek ve stejném workeru jej mohl využít dříve, než jej inicializace vynulovala. V obrazu: na stole přepážky zůstal lístek, který měl být už dávno uklizen. Podrobnosti zneužití záměrně nezveřejňujeme; zařazení najdete v CVE-2026-109619.

Přechodná opatření php.net k této zranitelnosti míří na potvrzené cesty, nejsou úplnou ochranou:

  1. session.upload_progress.enabled=0 v php.ini, poolu FPM nebo konfiguraci serveru, poté restartovat všechny workery; ne přes ini_set() nebo .user.ini.
  2. U PHP 8.5 navíc fatal_error_backtraces=0 a restart.
  3. Odstranit neplatná nebo zastaralá nastavení pro jednotlivé požadavky, která před inicializací vyvolávají hlášení. Pouhé skrytí hlášení podle php.net nepomáhá.
  4. V aplikaci odstranit zvenčí vyvolatelné fatální chyby a překročení časových limitů, i v callbacích, při zpracování nahrávání a importů.

Skutečnou nápravou je aktualizace na PHP 8.5.12, 8.4.27, 8.3.36 nebo 8.2.35. Protože chyba sídlí v běžícím workeru, projeví se až po restartu všech procesů; kroky najdete v článku Restart PHP-FPM a všech workerů.

Tip

Po restartu zkontrolujte pomocí ps -eo pid,lstart,cmd | grep php, zda byly všechny procesy PHP spuštěny po okamžiku aktualizace.

Co životní cyklus znamená pro provoz a vývoj

Z modelu lze odvodit jasná pravidla. Pro administrátory: aktualizace je účinná teprve tehdy, když neběží žádný proces se starým kódem. Každý provozní režim s dlouhověkými workery potřebuje zdokumentovaný restart. Pro vývojáře: kód, který běží v režimech worker, nesmí nechat stav vázaný na požadavek ve statických proměnných nebo singletonech, například uživatelská data nebo oprávnění. Pro oba platí: uchovejte logy od 20. 10. 2026, abyste mohli později posoudit, zda se před záplatou něco nestalo.

Jak konkrétně pomáhá PHP SOS?

Pomocí služby záplatování PHP model převádíme do praxe:

  • Určit provozní režim: Zjistíme, zda běží PHP-FPM, mod_php, LiteSpeed, FrankenPHP nebo php-cgi a jak se workery spravují.
  • Aktualizace a restart: Nasadíme opravenou verzi a řízeně restartujeme každého workera.
  • Kontrola procesů: Podle časů startu a verzí doložíme, že nezůstal žádný starý proces.
  • Prověřit režimy worker: U dlouho běžících aplikací se podíváme, zda se stav vázaný na požadavek čistě resetuje.

Časté dotazy

Spouští PHP pro každý požadavek nový proces?

Na příkazové řádce ano, ve webovém provozu většinou ne. PHP-FPM, Apache s mod_php, LiteSpeed nebo FrankenPHP nechají jeden proces vyřizovat mnoho požadavků po sobě. Mezi požadavky PHP stav požadavku uklidí.

Jaký je rozdíl mezi MINIT a RINIT?

MINIT proběhne jednou při startu procesu PHP a vytvoří trvalé struktury. RINIT proběhne na začátku každého jednotlivého požadavku a připraví to, co potřebuje jen tento požadavek.

Pomáhá pm.max_requests proti CVE-2026-109619?

php.net častější restarty workerů jako přechodné opatření neuvádí. Nastavení omezuje pouze to, kolik požadavků worker vyřídí, než se restartuje. Proti zranitelnosti pomáhá aktualizace s následným restartem všech workerů.

Co je režim worker u FrankenPHP?

V režimu worker se aplikace spustí jednou a zůstává v paměti. To šetří čas, ale statické proměnné, globální proměnné a cache zůstávají mezi požadavky zachovány. Podle dokumentace FrankenPHP je proto nutné stav vázaný na požadavek vědomě resetovat.

Zdroje

Okamžitý kontakt

Každá hodina se počítá. Napište nám hned.

Stručně popište systém, verzi PHP a pozorování. Ozveme se, vyjasníme situaci a budeme pracovat na dálku, dokud nebude incident uzavřen a zdokumentován.

Okamžitá pomoc