# Cykl życia żądania: start, wykonanie, shutdown

> Cykl życia żądania PHP wyjaśniony: MINIT, RINIT, RSHUTDOWN, MSHUTDOWN, ponowne użycie workerów i dlaczego CVE-2026-109619 dotyczy właśnie tego miejsca.

Źródło: https://phpsos.de/pl/php/request-lebenszyklus/  
Stan: 25.10.2026  
Dostawca: PHP SOS, Pomoc awaryjna PHP i bezpieczeństwo serwerów. Natychmiastowa pomoc przez WhatsApp: https://wa.me/491736494914?text=Dzie%C5%84%20dobry%2C%20PHP%20SOS%2C%20potrzebujemy%20pomocy%20z%20naszym%20serwerem%20PHP.%20Firma%3A%20%E2%80%A6%20Strona%20internetowa%3A%20%E2%80%A6

**Krótko:** Cykl życia żądania PHP ma dwa poziomy. Raz na proces PHP ładuje swoje rozszerzenia (faza MINIT) i na końcu je zwalnia (MSHUTDOWN). Dla każdego pojedynczego żądania istnieje osobna faza startu i sprzątania (RINIT i RSHUTDOWN). W pracy webowej worker obsługuje wiele żądań po kolei, dlatego PHP musi między dwoma żądaniami niezawodnie resetować cały stan żądania. Właśnie na tej granicy leżała CVE-2026-109619, druga luka PHP2Shell.

## Dwa poziomy: proces i żądanie

Dla osób nietechnicznych pomocny jest obraz: worker PHP jest jak okienko w urzędzie. Okienko jest rano raz urządzane, z formularzami, pieczątkami i komputerem. Potem przychodzi po kolei wielu interesantów. Po każdym interesancie biurko musi być puste, aby następny nie zobaczył cudzych dokumentów.

PHP działa tak samo. Przy starcie procesu ładuje rozszerzenia i ustawienia, które są takie same dla wszystkich żądań. Każde żądanie dostaje wtedy własną pamięć, własne zmienne i własne dane żądania, takie jak `$_GET`, `$_POST` czy `$_FILES`. Na końcu żądania PHP zwalnia tę pamięć. PHP Internals Book ujmuje to krótko: PHP zapomina pamięć żądania na końcu każdego żądania.

## Fazy cyklu życia żądania PHP w skrócie

Nazwy pochodzą z wnętrza PHP; każde rozszerzenie może się podpiąć pod te fazy.

| Faza | Kiedy się wykonuje | Jak często | Typowe zadanie |
|---|---|---|---|
| MINIT (Module Init) | przy starcie procesu | raz na proces | rejestracja rozszerzeń, zakładanie wpisów ini, dane trwałe |
| GINIT | przy zakładaniu zmiennych globalnych | na proces, przy wątkach na wątek | przygotowanie zmiennych globalnych rozszerzenia |
| RINIT (Request Init) | na początku każdego żądania | na żądanie | przygotowanie pamięci i stanu żądania |
| Wykonanie | pomiędzy | na żądanie | działa Państwa kod PHP |
| RSHUTDOWN | na końcu każdego żądania | na żądanie | zwolnienie zasobów żądania |
| PRSHUTDOWN | po RSHUTDOWN | na żądanie, rzadko używany | ostatnie prace porządkowe |
| MSHUTDOWN | przy zakończeniu procesu | raz na proces | zwolnienie danych trwałych |

Ważne zdanie z PHP Internals Book dla twórców rozszerzeń: zmienne globalne nie są automatycznie czyszczone po każdym żądaniu. To, co ma być świeże dla każdego żądania, należy zresetować w RINIT. Ta reguła pokazuje, dlaczego granica między dwoma żądaniami jest miejscem krytycznym dla bezpieczeństwa.

Na końcu żądania kolejność jest również ustalona: najpierw wykonują się funkcje shutdown zarejestrowane za pomocą `register_shutdown_function()`, potem destruktory, następnie opróżniany jest bufor wyjścia. Dopiero po tym następuje RSHUTDOWN.

## Dlaczego workery są używane ponownie

W wierszu poleceń proces obsługuje dokładnie jedno żądanie i kończy się. W pracy webowej byłoby to zbyt wolne. PHP-FPM, Apache z mod_php, LiteSpeed i FrankenPHP utrzymują więc workery, które obsługują wiele żądań jedno po drugim. MINIT wykonuje się wtedy raz, a RINIT i RSHUTDOWN tysiące razy. Jak FPM steruje swoimi workerami, wyjaśnia strona [PHP-FPM wyjaśniony](https://phpsos.de/pl/php/php-fpm-erklaert/).

W PHP-FPM `pm.max_requests` określa, po ilu żądaniach worker jest restartowany. Domyślnie jest to `0`, czyli bez ograniczenia. Podręcznik podaje to ustawienie jako środek przeciw wyciekom pamięci w bibliotekach podmiotów trzecich:

```ini
; pula FPM: restartować workera po 500 żądaniach (przeciw wyciekom pamięci)
pm = dynamic
pm.max_children = 20
pm.max_requests = 500
```

Jeszcze dalej idzie tryb workera we FrankenPHP: tam cała aplikacja uruchamia się raz i pozostaje w pamięci. Stan aplikacji przetrwa wtedy także na poziomie PHP. Przykład pokazuje różnicę:

```php
<?php
function anfrageZaehler(): int
{
    static $zaehler = 0;   // w klasycznej pracy FPM dla każdego żądania znów 0
    return ++$zaehler;     // w trybie workera wartość liczy dalej między żądaniami
}
echo anfrageZaehler();
```

W trybie klasycznym wywołanie zawsze wypisuje 1, ponieważ PHP odrzuca pamięć żądania. W trybie workera wartość rośnie, dopóki worker żyje. Dokumentacja FrankenPHP radzi więc świadomie resetować stan związany z żądaniem między żądaniami.

## Gdzie uderza CVE-2026-109619

CVE-2026-109619 nie dotyczy kodu Państwa aplikacji, lecz samego PHP. Według php.net wewnętrzny stan należący do żądania mógł przetrwać zwolnienie swojej pamięci. Późniejsze żądanie w tym samym workerze mogło go użyć, zanim inicjalizacja go zresetowała. W obrazie: na biurku w okienku został leżeć świstek, który faktycznie był już sprzątnięty. Szczegółów wykorzystania świadomie nie publikujemy; ocena znajduje się pod hasłem [CVE-2026-109619](https://phpsos.de/pl/php2shell/cve-2026-109619/).

Środki przejściowe php.net dotyczące tej luki opierają się na potwierdzonych drogach i nie są pełną ochroną:

1. `session.upload_progress.enabled=0` w php.ini, puli FPM lub konfiguracji serwera, potem zrestartować wszystkie workery; nie przez `ini_set()` ani `.user.ini`.
2. W PHP 8.5 dodatkowo `fatal_error_backtraces=0` i restart.
3. Usunąć nieprawidłowe lub przestarzałe ustawienia dla każdego żądania, które przed inicjalizacją generują komunikaty. Samo ukrywanie komunikatów według php.net nie pomaga.
4. Naprawić w aplikacji błędy krytyczne i przekroczenia czasu, które można wywołać z zewnątrz, także w callbackach oraz przy przetwarzaniu przesłanych plików i importu.

Właściwym lekarstwem jest aktualizacja do PHP 8.5.12, 8.4.27, 8.3.36 lub 8.2.35. Ponieważ błąd tkwi w działającym workerze, działa ona dopiero po restarcie wszystkich procesów; kroki opisano w artykule [Restart PHP-FPM i wszystkich workerów](https://phpsos.de/pl/php2shell/php-fpm-neustart/).

> **Wskazówka:**
> Po restarcie proszę sprawdzić za pomocą `ps -eo pid,lstart,cmd | grep php`, czy wszystkie procesy PHP wystartowały po momencie aktualizacji.

## Co cykl życia oznacza dla eksploatacji i programowania

Z tego modelu można wyprowadzić jasne reguły. Dla administratorów: aktualizacja jest skuteczna dopiero wtedy, gdy nie działa już żaden proces ze starym kodem. Każdy tryb pracy z długowiecznymi workerami potrzebuje udokumentowanego restartu. Dla programistów: kod działający w trybach workera nie może zostawiać stanu związanego z żądaniem w zmiennych statycznych ani singletonach, na przykład danych użytkownika lub uprawnień. Dla obu grup obowiązuje: przechowywać logi od 20.10.2026, aby później móc ocenić, czy przed poprawką coś się wydarzyło.

## Jak konkretnie pomaga PHP SOS?

Za pomocą [usługi poprawek PHP](https://phpsos.de/pl/leistungen/php-patch-service/) wdrażamy ten model w praktyce:

- **Ustalenie trybu pracy:** Stwierdzamy, czy działa PHP-FPM, mod_php, LiteSpeed, FrankenPHP czy php-cgi i jak zarządza się workerami.
- **Aktualizacja i restart:** Instalujemy poprawioną wersję i kontrolowanie restartujemy każdego workera.
- **Kontrola procesów:** Na podstawie czasów startu i wersji dowodzimy, że nie pozostał żaden stary proces.
- **Sprawdzenie trybów workera:** W długo działających aplikacjach sprawdzamy, czy stan związany z żądaniem jest czysto resetowany.


## Częste pytania

**Czy PHP uruchamia nowy proces dla każdego żądania?**  
W wierszu poleceń tak, w pracy webowej zazwyczaj nie. PHP-FPM, Apache z mod_php, LiteSpeed czy FrankenPHP pozwalają jednemu procesowi obsługiwać wiele żądań po kolei. Między żądaniami PHP sprząta stan żądania.

**Jaka jest różnica między MINIT a RINIT?**  
MINIT wykonuje się raz, gdy startuje proces PHP, i zakłada trwałe struktury. RINIT wykonuje się na początku każdego pojedynczego żądania i przygotowuje to, czego potrzebuje tylko to żądanie.

**Czy pm.max_requests pomaga przeciw CVE-2026-109619?**  
php.net nie podaje częstszych restartów workerów jako środka przejściowego. To ustawienie ogranicza tylko, ile żądań worker obsłuży, zanim zostanie zrestartowany. Przeciw luce pomaga aktualizacja z następującym po niej restartem wszystkich workerów.

**Czym jest tryb workera we FrankenPHP?**  
W trybie workera aplikacja uruchamia się raz i pozostaje w pamięci. Oszczędza to czas, ale zmienne statyczne, zmienne globalne i cache zostają zachowane między żądaniami. Według dokumentacji FrankenPHP stan związany z żądaniem trzeba więc świadomie resetować.

## Źródła

- [php.net: komunikat o bezpieczeństwie z 21.10.2026](https://www.php.net/)
- [Podręcznik PHP: konfiguracja FPM](https://www.php.net/manual/en/install.fpm.configuration.php)
- [PHP Internals Book: cykl życia PHP](https://www.phpinternalsbook.com/)
