Ви оновилися до Ubuntu 24.04, усе здавалося в порядку, а потім сайт почав падати із 502 Bad Gateway, наче це тренування з аварійного відновлення. Ви перезапускаєте php-fpm, він працює кілька хвилин, а потім знову падає. Ви дивитеся в логи Nginx, ніби вони вам винні гроші.
Ось реальність: аварії PHP-FPM рідко бувають «таємничими». Зазвичай вони знаходяться на відстані одного вирішального рядка в журналі. Знайдіть цей рядок — і ви припините гадати й почнете виправляти.
Рядок журналу, який потрібно знайти (і чому це важливо)
Якщо PHP-FPM «падає», вам потрібен рядок, який каже хто його вбив або який сигнал його завершив. Не Nginx 502. Не попередження WordPress. Не стек-трейс вашого додатку (ще). Рядок, який потрібно знайти, — один із цих:
- Рядок OOM killer ядра:
Out of memory: Killed process ... (php-fpm...)або записи зoom-kill:. - Рядок результату systemd:
Main process exited, code=killed, status=9/KILLабоstatus=11/SEGV. - Краш дочірнього процесу PHP-FPM:
child ... exited on signal 11 (SIGSEGV)абоchild ... exited with code 255. - Помилки сокета/прослуховування:
unable to bind listening socket,Address already in use,Permission denied.
Ці рядки визначають вашу гілку реальності:
- OOM killer? Перестаньте підлаштовувати PHP і почніть підбирати пам’ять,
pm.max_childrenі поведінку пам’яті на запит. - SIGSEGV? Ставтеся до цього як до нативного крашу: баг розширення, крайній випадок Opcache JIT, пошкоджена спільна пам’ять або некоректна збірка модуля.
- Status 9/KILL без OOM? Ймовірно watchdog/timeouts systemd, скрипти адміністрування, cgroup-ліміти або ліміти контейнера.
- Проблеми прив’язки сокета/дозволів? Це помилка розгортання/конфігурації, а не питання продуктивності.
Цитата, варта стікеру, бо вона дисциплінує:
Парафразована думка — Werner Vogels: «Усе ламається; будувати системи, які очікують відмов і швидко відновлюються.»
Ми робитимемо частину «очікування»: спочатку знайдіть правильний рядок, потім виправте правильну проблему. Ваш бюджет часу безвідмовності заслуговує кращого, ніж «перезапуск і надія».
Швидкий план діагностики (перший/другий/третій)
Це швидкий шлях, коли продакшн кровоточить і у вас немає часу на інтерпретативні танці з логами.
Перший крок: підтвердіть, що «падіння» означає в очах systemd
- Перевірте стан сервісу й останній код виходу.
- Вирішіть: OOM проти сигналу проти помилки конфігурації проти проблеми залежностей.
Другий крок: знайдіть вбивцю (kernel OOM, systemd kill або сегфолт)
- Шукайте в
journalctlпоoom,killed process,SEGV,SIG. - Якщо бачите OOM: зупиніться й виправляйте пам’ять і конкуренцію. Не «оптимізуйте PHP» одразу.
Третій крок: корелюйте з журналами Nginx і пулів, щоб оцінити радіус ураження
- Чи збігаються помилки з піками трафіку, cron-завданнями, деплоєм або бекапами?
- Це один пул чи всі пули?
- Чи запити були повільні/зависали перед падінням? Якщо так — увімкніть slowlog і таймаути.
Ось і все. Якщо ви не знаходите вбивчий рядок за 10 хвилин, ймовірно, дивитеся не там або логи неправильно спрямовані.
Цікаві факти та контекст (що пояснює сьогоднішню дивність)
- PHP-FPM не завжди був «стандартним PHP». FastCGI Process Manager починався як сторонній набір патчів перед тим, як його вмержили в ядро PHP роки тому, що пояснює наявність «спадкових» регуляторів, які досі існують.
- systemd змінив робочий процес усунення неполадок.Журнал часто містить істину навіть коли логи додатка — ні, бо systemd зберігає коди виходу, сигнали й цикли перезапуску.
- Код виходу 139 зазвичай — це сегфолт. За конвенціями Linux, 128 + номер сигналу; сигнал 11 (SIGSEGV) дає 139. Це не магія, це арифметика з наслідками.
- OOM killer — це політичне рішення ядра, а не баг. Ядро вбиває щось, щоби зберегти систему живою. Якщо воно вибрало PHP-FPM, це каже вам, який процес був «найбільш вбиваним» під навантаженням.
- Opcache — це фіча продуктивності, яка живе в спільній пам’яті. Коли вона некоректно працює, може впасти кілька воркерів таким чином, що виглядає випадково, бо спільний стан — спільний знаменник.
- Unix-сокети швидші, але суворіші. TCP-слухачі поблажливі щодо дозволів; Unix-сокети — ні. Один невірний режим/власник — і Nginx кричатиме, а PHP-FPM буде наполягати, що він «працює».
pm.max_children— не «скільки у вас ядер CPU». Це скільки паралельних процесів PHP ви дозволяєте, і пам’ять зазвичай є реальним обмежувачем значно раніше, ніж CPU.- Оновлення Ubuntu можуть тихо змінювати значення за замовчуванням. Нові збірки PHP, інші налаштування systemd і оновлені бібліотеки OpenSSL/ICU можуть змінити поведінку і стабільність розширень.
Жарт №1: PHP-FPM схожий на кав’ярню — якщо впустити нескінченну кількість клієнтів при одному баристі, ваша «латентність» стане стилем життя.
Практичні завдання: команди, виводи та рішення
Ви хотіли реальні завдання, а не відчуття. Кожне завдання нижче містить: команду, реалістичний приклад виводу, що це означає, і яке рішення прийняти.
Завдання 1: Перевірити стан сервісу й код виходу
cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Loaded: loaded (/usr/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
Active: failed (Result: signal) since Mon 2025-12-29 09:14:11 UTC; 2min 3s ago
Process: 18244 ExecStart=/usr/sbin/php-fpm8.3 --nodaemonize --fpm-config /etc/php/8.3/fpm/php-fpm.conf (code=killed, signal=SEGV)
Main PID: 18244 (code=killed, signal=SEGV)
CPU: 2.114s
Значення: systemd зафіксував, що PHP-FPM помер від SIGSEGV. Це не «воно стало повільним». Воно впало.
Рішення: Досліджуйте розширення/Opcache/JIT/core dump, а не спочатку налаштування таймаутів.
Завдання 2: Витягти вирішальні рядки з журналу (остання завантаження)
cr0x@server:~$ journalctl -u php8.3-fpm -b -n 200 --no-pager
Dec 29 09:14:11 server systemd[1]: php8.3-fpm.service: Main process exited, code=killed, status=11/SEGV
Dec 29 09:14:11 server systemd[1]: php8.3-fpm.service: Failed with result 'signal'.
Dec 29 09:14:11 server systemd[1]: php8.3-fpm.service: Scheduled restart job, restart counter is at 5.
Dec 29 09:14:11 server systemd[1]: Stopped php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager.
Значення: Повторюваний сегфолт; systemd перезапускає в циклі.
Рішення: Стабілізувати, відключивши підозрілі фічі (JIT), розглянути core dump і знайти запит/розширення, яке падає.
Завдання 3: Перевірити наявність доказів OOM killer у ядрі
cr0x@server:~$ journalctl -k -b | grep -Ei 'oom|out of memory|killed process' | tail -n 20
Dec 29 08:57:02 server kernel: oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/php8.3-fpm.service,task=php-fpm8.3,pid=17602,uid=33
Dec 29 08:57:02 server kernel: Out of memory: Killed process 17602 (php-fpm8.3) total-vm:1884320kB, anon-rss:812344kB, file-rss:0kB, shmem-rss:0kB, UID:33 pgtables:2308kB oom_score_adj:0
Dec 29 08:57:03 server kernel: systemd[1]: php8.3-fpm.service: Main process exited, code=killed, status=9/KILL
Значення: PHP-FPM було вбито OOM, а не «випав випадково». SEGV у Завданні 2 може бути іншим інцидентом; не змішуйте часові ланцюги.
Рішення: Розглядайте це як проблему ємності/конкуренції: зменшіть pm.max_children, виправте витоки пам’яті або додайте пам’ять/своп. Також перевірте, чи PHP-FPM у cgroup з обмеженням пам’яті.
Завдання 4: Визначити, які пул(и) задіяні
cr0x@server:~$ ls -1 /etc/php/8.3/fpm/pool.d/
www.conf
api.conf
worker.conf
Значення: Кілька пулів. Збої можуть бути ізольовані в одному пулі з іншими налаштуваннями.
Рішення: Звузьте область пошуку: знайдіть, в якому пулі логи показують завершення дочірніх процесів; не припускайте «PHP впав», якщо один пул пошкоджено.
Завдання 5: Перевірити логи пулів PHP-FPM на предмет загибелі дочірніх процесів і повільних запитів
cr0x@server:~$ grep -R --line-number -E 'SIGSEGV|exited on signal|server reached pm\.max_children|slowlog|terminated' /var/log/php8.3-fpm.log | tail -n 30
14592:WARNING: [pool api] child 19044 exited on signal 11 (SIGSEGV) after 3.221146 seconds from start
14593:NOTICE: [pool api] child 19061 started
14610:WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
14625:WARNING: [pool www] child 19104, script '/var/www/site/index.php' (request: "GET /") executing too slow (5.112 sec), logging
Значення: Дві окремі проблеми: пул api падає з сегфолтом; пул www має обмеження по ємності й є повільним.
Рішення: Розподіліть роботу: стабілізуйте пул api (розширення/JIT), потім налаштуйте конкуренцію/таймаути для www.
Завдання 6: Підтвердити вирівнювання сокета listen і upstream Nginx
cr0x@server:~$ ss -xlpn | grep -E 'php|fpm'
u_str LISTEN 0 4096 /run/php/php8.3-fpm.sock 32564 * 0 users:(("php-fpm8.3",pid=19212,fd=8))
Значення: Сокет існує і на ньому слухає PHP-FPM.
Рішення: Якщо Nginx все ще повертає 502, перевірте дозволи на сокет і користувача/групу Nginx; це не відсутній сокет.
Завдання 7: Перевірити власника та режим сокета
cr0x@server:~$ stat -c '%n %U:%G %a' /run/php/php8.3-fpm.sock
/run/php/php8.3-fpm.sock www-data:www-data 660
Значення: Сокет належить www-data і групі має права на запис; типово для Nginx, який працює як www-data.
Рішення: Якщо Nginx працює як користувач nginx, додайте його в групу сокета або налаштуйте listen.owner/listen.group/listen.mode в пулі.
Завдання 8: Перевірити лог помилок Nginx для точного upstream-повідомлення
cr0x@server:~$ tail -n 30 /var/log/nginx/error.log
2025/12/29 09:14:12 [error] 20220#20220: *884 connect() to unix:/run/php/php8.3-fpm.sock failed (111: Connection refused) while connecting to upstream, client: 198.51.100.24, server: example, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example"
2025/12/29 09:14:20 [error] 20220#20220: *901 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 198.51.100.29, server: example, request: "POST /api HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example"
Значення: Перша помилка: PHP-FPM не приймав з’єднань (вимкнений або перезапускається). Друга помилка: PHP-FPM прийняв, але не відповів вчасно.
Рішення: Виправте петлю крашу/перезапуску першою. Таймаути часто — симптом нестачі воркерів або блокованого I/O.
Завдання 9: Перевірити, чи PHP-FPM має ліміт пам’яті від systemd/cgroups
cr0x@server:~$ systemctl show php8.3-fpm -p MemoryMax -p MemoryHigh -p TasksMax -p OOMPolicy
MemoryMax=536870912
MemoryHigh=0
TasksMax=512
OOMPolicy=stop
Значення: Для сервісу встановлено ліміт 512 MiB. Під навантаженням ви досягнете його, навіть якщо на хості є вільна оперативна пам’ять.
Рішення: Підніміть або видаліть ліміт (обережно), а потім перегляньте налаштування пулів. Якщо ви не встановлювали його, проведіть аудит, хто це зробив.
Завдання 10: Виміряти пам’ять на воркер, щоб адекватно встановити pm.max_children
cr0x@server:~$ ps -o pid,rss,cmd -C php-fpm8.3 --sort=-rss | head -n 8
PID RSS CMD
19244 148320 php-fpm: pool www
19241 142908 php-fpm: pool www
19262 139440 php-fpm: pool api
19251 136112 php-fpm: pool www
19212 31240 php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)
Значення: Воркері споживають приблизно 135–150 MiB RSS під поточним навантаженням. 20 дітей означає приблизно 3 ГіБ лише для воркерів, плюс Opcache і все інше.
Рішення: Якщо у вас 2–4 ГіБ RAM, pm.max_children=20 — це фантазія. Зменшіть кількість дітей або зменшіть пам’ять на запит (додаток/ліміти), або додайте RAM.
Завдання 11: Увімкнути slowlog PHP-FPM, щоб зафіксувати «воно зависло, а потім впало»
cr0x@server:~$ sudo grep -nE 'slowlog|request_slowlog_timeout' /etc/php/8.3/fpm/pool.d/www.conf
55:request_slowlog_timeout = 5s
56:slowlog = /var/log/php8.3-fpm-www-slow.log
Значення: Slowlog спрацьовує через 5с. Це достатньо жорстко, щоб зловити патологічні запити, не потопивши вас логами.
Рішення: Якщо у вас нема slowlog під час інцидентів, ви свідомо обираєте невідомість. Увімкніть його у постраждалих пулах і відтворюйте під навантаженням.
Завдання 12: Підтвердити версію PHP і завантажені модулі для підозрілих елементів
cr0x@server:~$ php-fpm8.3 -v
PHP 8.3.6 (fpm-fcgi) (built: Nov 21 2025 10:14:22)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies
cr0x@server:~$ php -m | egrep -i 'opcache|redis|imagick|swoole|xdebug'
imagick
opcache
redis
Значення: Opcache присутній (очікувано). Imagick часто спричиняє «нативні краші», бо зв’язується з бібліотеками ImageMagick; redis також може мати версіонно-залежну поведінку.
Рішення: Якщо бачите SIGSEGV: тимчасово вимкніть розширення з високим ризиком у падаючому пулі, щоб ізолювати проблему. Мета — зупинити падіння, потім поетапно повертати функціональність.
Завдання 13: Перевірити обробку core dump (щоб краші стали дієвими для налагодження)
cr0x@server:~$ coredumpctl list php-fpm8.3 | head
TIME PID UID GID SIG COREFILE EXE
Mon 2025-12-29 09:14:11 UTC 18244 0 0 11 present /usr/sbin/php-fpm8.3
Значення: Ядро-кор-файл існує. Це золото для налагодження нативних крашів.
Рішення: Якщо можливо, витягніть backtrace у стейджингу з дебаг-символами; у продакшні принаймні збережіть core і зіставте з недавніми деплоями і увімкненими модулями.
Завдання 14: Перевірити ліміти дескрипторів файлів (тендітний вектор крашів/нестабільності)
cr0x@server:~$ systemctl show php8.3-fpm -p LimitNOFILE
LimitNOFILE=1024
Значення: 1024 відкритих файлів може бути замало для завантажених сайтів (сокети, файли, логи, upstream). Коли ви його досягнете, отримаєте дивні збої: failed accepts, failed opens, інколи каскадні таймаути.
Рішення: Підніміть LimitNOFILE через systemd override, якщо ви на масштабі; потім перевірте це під час роботи.
Завдання 15: Перевірити відмови AppArmor (Ubuntu їх любить)
cr0x@server:~$ journalctl -k -b | grep -i apparmor | tail -n 10
Dec 29 09:02:41 server kernel: audit: type=1400 apparmor="DENIED" operation="open" profile="/usr/sbin/php-fpm8.3" name="/srv/secrets/api-key.txt" pid=18011 comm="php-fpm8.3" requested_mask="r" denied_mask="r" fsuid=33 ouid=0
Значення: Процес PHP-FPMу відмовлено в доступі. Це може спричинити фатальні помилки, нескінченні повтори або каскадні відмови залежно від поведінки додатка.
Рішення: Виправте шляхи та дозволи або налаштуйте профіль AppArmor. Не «chmod 777» щоб уникнути проблем.
Завдання 16: Перевірити конфігурацію перед перезапуском (щоб не штовхати синтаксично зіпсоване)
cr0x@server:~$ php-fpm8.3 -t
[29-Dec-2025 09:20:55] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful
Значення: Синтаксис у порядку. Це не гарантує правильність семантики, але запобігає принизливим перезапускам.
Рішення: Робіть php-fpm -t частиною пайплайнів деплою та перед хукамі перезапуску.
Сценарії відмов на Ubuntu 24.04: як вони виглядають
Сценарій A: OOM kills (класичне «перезапустився, потім знову помер»)
Симптоми:
- Перебійні 502 під навантаженням, гірше під час піків трафіку або пакетних завдань.
status=9/KILLу systemd,Out of memory: Killed processв логах ядра.- Воркері показують великий RSS; використання пам’яті росте до раптової загибелі.
Механіка:
- Кожен воркер — це процес зі своїм споживанням пам’яті. При
pm=dynamicконкуренція зростає з попитом, і пам’ять росте разом з нею. - Запити можуть виділяти багато пам’яті для JSON, ORM-hydration, обробки зображень, генерації PDF або просто витоку.
- Якщо ви запускаєте все на одній ВМ (веб + БД + кеши), ви організовуєте бійку за RAM.
Сценарій B: Сегфолти (код виходу 139, SIGSEGV)
Симптоми:
- systemd показує
status=11/SEGVабо лог говорить про child exited on SIGSEGV. - Часто прив’язане до конкретного маршруту, типу завантаження, операції зображення або виклику розширення.
- Може початися після оновлення: мінорна версія PHP, оновлення бібліотеки або перебудова розширення.
Механіка:
- PHP сам по собі написаний на C. Розширення — на C. Сегфолт означає, що хтось торкнувся пам’яті не за правилами. PHP userland може спровокувати це опосередковано.
- Opcache і JIT можуть посилювати рідкі баги, бо змінюють шляхи виконання і макет пам’яті.
- Змішані пакети або застарілі
.so-модулі викликають ABI-несумісності; може працювати деякий час, а потім падати.
Сценарій C: Прив’язка сокета та дозволи (не крах, але виглядає як крах)
Симптоми:
- PHP-FPM не стартує: «unable to bind listening socket».
- Помилка в логу Nginx:
connect() ... failed (13: Permission denied).
Механіка:
- Застарілий файл сокета або неправильна адреса
listen. - Пул працює під іншим користувачем, ніж очікувалося; Nginx не може доступитися до сокета.
- Два пули намагаються прив’язатися до того самого шляху сокета.
Сценарій D: «Не впав» — просто завис або насичений
Симптоми:
- Nginx каже upstream timed out; PHP-FPM залишається «active (running)».
- Рядок у логах:
server reached pm.max_children. - Slowlog показує одну і ту ж функцію як вузьке місце: виклики до БД, мережеві виклики, I/O файлової системи.
Механіка:
- У вас закінчилися воркери. Черга накопичується. Nginx чекає. Клієнти йдуть.
- Або воркери є, але вони заблоковані на зовнішньому ресурсі і не повертаються до пулу.
Виправлення, які реально працюють (OOM, сегфолти, сокети, ліміти)
Пакет виправлень 1: Зупинити OOM kill, підібравши конкуренцію під пам’ять
Неприваблива правда: більшість «падінь» PHP-FPM під навантаженням — самозавдані, коли дозволяють більше конкуренції, ніж витримує RAM.
1) Зменшити pm.max_children на основі спостережуваного RSS
Використайте Завдання 10, щоб виміряти реалістичний RSS під типовим і піковим навантаженням. Потім порахуйте. Якщо воркери в середньому 140 MiB RSS і ви можете виділити 1.5 GiB для воркерів PHP, вам близько ~10 дітей, а не 30.
У файлі пулу:
cr0x@server:~$ sudo grep -nE 'pm\.max_children|pm\.start_servers|pm\.min_spare_servers|pm\.max_spare_servers' /etc/php/8.3/fpm/pool.d/www.conf
90:pm.max_children = 20
91:pm.start_servers = 4
92:pm.min_spare_servers = 2
93:pm.max_spare_servers = 6
Рішення: Зменшіть pm.max_children до безпечного числа, потім спостерігайте черги й латентність. Краще ставити в чергу, ніж помирати.
2) Встановіть ліміт пам’яті на запит (стратегічно)
Не ставте memory_limit «величезним», бо одна сторінка звіту потребує цього. Так ви даєте кожному воркеру гранату. Натомість обробляйте важкі завдання асинхронно або ізолюйте їх у виділеному пулі зі строгим контролем.
3) Усунути антипатерн «один пул для всього»
Створіть окремі пули для:
- Публічних веб-запитів (чутливі до латентності)
- API-запитів (часто сплески)
- Адмін/cron/фон (пам’яттєво затратні, терпиміші до затримок)
Це обмежить радіус ураження. Неконтрольований імпорт адміну не повинен витісняти вашу домашню сторінку з пам’яті.
4) Перевірте ліміти пам’яті systemd, якщо вони є
Якщо встановлений MemoryMax (Завдання 9), підправте його через drop-in override. Це хірургічна зміна, а не обряд.
cr0x@server:~$ sudo systemctl edit php8.3-fpm
# (editor opens; add the following)
# [Service]
# MemoryMax=0
# LimitNOFILE=65535
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart php8.3-fpm
cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Active: active (running) since Mon 2025-12-29 09:28:12 UTC; 3s ago
Рішення: Якщо ви видаляєте ліміти, компенсуйте це реалістичним pm.max_children і моніторингом, інакше ви просто перемістите відмову в «OOM у всій ВМ».
Пакет виправлень 2: Зупинити сегфолти, ізолювавши винуватця
Сегфолти врешті-решт детерміновані. Ваше завдання — звузити простір пошуку.
1) Тимчасово вимкніть JIT, якщо він увімкнений
JIT може бути швидким; він також може бути підсилювачем крашів у крайніх випадках. Якщо ви не знаєте, чи JIT увімкнено, припустіть, що хтось його вмикав «для продуктивності» й забув.
cr0x@server:~$ php -i | grep -i opcache.jit
opcache.jit => tracing => tracing
opcache.jit_buffer_size => 128M => 128M
Вимкніть його (тимчасово) у /etc/php/8.3/fpm/conf.d/10-opcache.ini або через окремий override:
cr0x@server:~$ sudo sed -n '1,120p' /etc/php/8.3/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.jit=tracing
opcache.jit_buffer_size=128M
Рішення: Встановіть opcache.jit=0 і opcache.jit_buffer_size=0, перезапустіть і перевірте, чи краші зникли. Якщо так — ви знайшли важел.
2) Вимкніть розширення з високим ризиком по пулах
Ви можете контролювати деякі налаштування через php_admin_value у пулах, але завантаження модулів зазвичай глобальне. Практично ізолюють шляхом запуску окремого екземпляра FPM або окремого пулу з іншим php.ini, де це можливо, або тимчасовим видаленням розширення в maintenance-вікні.
Якщо Imagick задіяний і краші корелюють з обробкою зображень — протестуйте видалення в стейджингу або тимчасово відключіть відповідний флаг у додатку.
3) Зібрати core dump і backtrace (дорослий підхід)
Коли краш дорогий, припиніть гадати. Використайте coredumpctl (Завдання 13) і дістаньте інформацію. Навіть без повних символів можна часто побачити ім’я модуля, що падає.
4) Очистити змішані пакети / застарілі модулі після оновлення
Оновлення Ubuntu можуть залишити старі модулі. Переконайтеся, що ви не завантажуєте розширення, зібране для іншого мінорного релізу PHP.
cr0x@server:~$ php -i | grep -E '^extension_dir'
extension_dir => /usr/lib/php/20230831 => /usr/lib/php/20230831
Рішення: Переконайтеся, що extension_dir відповідає встановленій PHP API версії і що модулі в цій директорії походять з одного збірного набору.
Пакет виправлень 3: Зробити сокети і дозволи нудними
Проблеми зі сокетами відбирають час, бо виглядають як «PHP вимкнений», тоді як PHP-FPM вважає, що він у порядку.
1) Переконайтеся, що кожний пул має унікальний listen socket
Якщо два пули слухають один і той самий шлях сокета, один перемагає, інший падає, і ваш інцидент стає лотереєю.
cr0x@server:~$ grep -R --line-number '^listen\s*=' /etc/php/8.3/fpm/pool.d/
/etc/php/8.3/fpm/pool.d/www.conf:34:listen = /run/php/php8.3-fpm.sock
/etc/php/8.3/fpm/pool.d/api.conf:34:listen = /run/php/php8.3-fpm.sock
Рішення: Виправте негайно: дайте кожному пулу свій сокет (або TCP-порт) і оновіть upstream-и Nginx відповідно.
2) Узгодьте користувача Nginx з правами сокета
Перевірте користувача Nginx:
cr0x@server:~$ grep -nE '^\s*user\s+' /etc/nginx/nginx.conf
1:user www-data;
Рішення: Якщо Nginx працює як nginx, то встановіть listen.group=nginx (або додайте nginx в групу www-data) і тримайте права 660.
Пакет виправлень 4: Зупинити цикли перезапуску від перетворення в відмови
Цикли перезапуску systemd корисні, поки стають шкідливими. Якщо PHP-FPM падає миттєво, systemd буде продовжувати спроби, їсти CPU, засмічувати логи і робити досвід клієнтів «крутим».
1) Сповільніть цикл перезапуску, поки налагоджуєте
Додайте systemd override з RestartSec. Це дає час і зменшує шум у логах.
cr0x@server:~$ sudo systemctl edit php8.3-fpm
# [Service]
# RestartSec=5s
Рішення: Використовуйте це тимчасово як ремінь безпеки, а не як довготермінове рішення. Мета не «перезапускати повільніше», а «припинити падати».
2) Використовуйте reload, коли це безпечно; restart — коли потрібно
Reload — м’якший варіант; restart — молоток. Для конфігурацій, які PHP-FPM може перезавантажити, робіть:
cr0x@server:~$ sudo systemctl reload php8.3-fpm
cr0x@server:~$ journalctl -u php8.3-fpm -n 5 --no-pager
Dec 29 09:33:10 server systemd[1]: Reloaded php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager.
Рішення: Якщо підозрюєте корупцію в пам’яті або проблеми розширень, перезапуск безпечніший. Reload не врятує від отруєного стану процеса.
Жарт №2: Перезапуск PHP-FPM кожні п’ять хвилин — це не «самовилікування». Це «самозаспокоєння», і воно не вражає on-call команду.
Три міні-історії з корпоративного життя (що справді йде не так)
Міні-історія 1: Інцидент через неправильне припущення
Вони мігрували середній e-commerce стек на Ubuntu 24.04 протягом вихідних. План змін був продуманий: нові AMI, blue/green, канареї. У понеділок канареї виглядали нормально. До обіду підтримка загуділа: спорадичні відмови оформлення з 502.
Інженер на чергуванні припустив, що це «мережеві проблеми», бо помилки були періодичними і згрупованими біля виклику платіжного API. Вони ганяли TLS, кешування DNS, навіть conntrack брандмауера. Розумні, зайняті і неправі.
Справжня підказка була у журналі ядра: OOM kill проти php-fpm. Новий базовий образ мав systemd drop-in з MemoryMax для «жорсткого» обмеження. Його скопіювали з шаблону для меншого сервісу, де це мало сенс. Тут це стало пасткою. Під реальним трафіком кілька великих запитів корзини підштовхнули воркерів PHP до межі cgroup.
Після того, як перестали припускати «мережу», виправлення зайняло годину: підняти ліміт пам’яті, зменшити pm.max_children по виміряному RSS і винести admin-pool для імпортів. Також додали оповіщення по OOM kill. Не тому, що це модно. Тому що це той рядок журналу, який закінчує суперечки.
Цікаво було не виправлення, а режим відмови. Перебійні 502 можуть бути просто питанням ємності. Інтуїція намагатиметься звинуватити мережу. Ядро тихо скаже, що це пам’ять.
Міні-історія 2: Оптимізація, що пішла не так
Інша команда мала проблему з латентністю. Сторінки стали повільними після апгрейду додатку. Хтось запропонував увімкнути Opcache JIT, бо читав, що це може прискорити CPU-інтенсивні навантаження. Увімкнули глобально, збільшили буфер і оголосили перемогу після швидкого бенчмарку.
Через два дні почалися випадкові сегфолти воркерів PHP-FPM. Не постійні, не відразу відтворювані. Достатньо, щоб спричинити перезапуски в пікові години і перетворити це на помітні клієнтам помилки. Логи показали child exited on signal 11. Це той рядок, який змушує довго дивитися в стелю.
Вони зробили правильне: відкотили JIT і краші зупинилися. Це не довело, що JIT «поганий». Це довело, що зміна взаємодіяла з їхнім набором розширень і профілем запитів так, як бенчмарк не охоплював. Бенчмарк бив по щасливому шляху. Продакшн завжди б’є по дивному шляху.
Постмортем був суворий: фічі продуктивності — це зміни, а не безкоштовний обід. Вони ввели політику: вмикати JIT тільки по пулах, за контрольованим rollout-ом і з моніторингом рівня крашів. Кумедно, але політика виявилася дешевшою за інцидент.
Міні-історія 3: Сухо, але правильна практика, що врятувала
Одна SaaS-компанія запускала кілька пулів PHP: www для інтерактивного трафіку, api для партнерів і jobs для фон. Кожен пул мав свій сокет, власний slowlog і жорсткі таймаути. Це не було гламурно. Саме тому їхній інцидент був лише помірно неприємним.
Одного дня фонове завдання почало генерувати величезні PDF через погане зміст шаблону. Використання пам’яті на запит подвоїлося. Пул jobs почав досягати свого pm.max_children і став чергуватися. Воркери крутилися. Але публічний сайт залишився працювати.
Оскільки радіус ураження був обмежений, on-call міг налагоджувати без пожежі для клієнтів. Slowlog зафіксував стеки для шляху генератора PDF. Вони відкотили шаблон, очистили чергу завдань і потім зменшили ліміт пам’яті для того пулу, щоб він упав швидко наступного разу.
Урок не в тому, щоб «бути хитрим». Він у тому, щоб «бути розділеним». Коли PHP-FPM падає, ізоляція — ваш демпфер. Окремі пули — це операційні автоматичні вимикачі, які коштують майже нічого в порівнянні з часом простою.
Типові помилки: симптом → корінь проблеми → виправлення
1) Симптом: Nginx показує 502; PHP-FPM «active (running)»
Корінь проблеми: Неправильний шлях сокета в Nginx, або невідповідність дозволів на сокет, або Nginx вказує на пул, який не слухає.
Виправлення: Використайте ss -xlpn щоб підтвердити точний сокет; перевірте, що upstream Nginx збігається; перевірте stat власника/режим; узгодьте listen.owner/listen.group/listen.mode.
2) Симптом: PHP-FPM постійно перезапускається; systemd показує status=9/KILL
Корінь проблеми: OOM kill ядра або enforcement cgroup MemoryMax.
Виправлення: Перевірте journalctl -k на OOM-рядки; інспектуйте systemctl show на ліміти пам’яті; зменшіть pm.max_children; виправте пам’яттєво важкі ендпоїнти; додайте RAM за потреби.
3) Симптом: Код виходу 139 / status=11/SEGV
Корінь проблеми: Нативний краш у PHP або розширенні, часто пов’язаний з оновленнями бібліотек, Opcache/JIT, Imagick або ABI-несумісностями.
Виправлення: Вимкніть JIT; видаліть/вимкніть підозрілі розширення; зберіть core dump через coredumpctl; забезпечте послідовний набір пакетів; відтворіть на стейджингу з тими ж бінарями.
4) Симптом: «server reached pm.max_children» і зростаюча латентність
Корінь проблеми: Недостатньо воркерів для конкуренції запитів, або воркери заблоковані на I/O (БД, мережа, файлові операції), тому не повертаються в пул.
Виправлення: Використайте slowlog, щоб знайти блокуючі виклики; виправте індекси БД або зовнішні виклики; налаштовуйте pm.max_children тільки після підтвердження пам’яттєвого запасу; розгляньте кешування й асинхронні задачі.
5) Симптом: PHP-FPM не стартує після оновлення
Корінь проблеми: Помилка в конфігурації пулу, дублювання listen сокетів або застарілий pid/socket файл.
Виправлення: Запустіть php-fpm -t; grep по файлах пулів на дублікат listen; видаліть застарілий сокет при потребі; перезапустіть чисто.
6) Симптом: Випадкове «Permission denied» на файлах, які «повинні бути читабельними»
Корінь проблеми: AppArmor відмовляє або неправильний user/group у пулі.
Виправлення: Перевірте журнали аудиту ядра на відмови AppArmor; тримайте секрети у схвалених локаціях; налаштуйте профіль або шлях; не розширюйте права надто широко.
7) Симптом: Працює годинами, а потім починає падати до перезапуску
Корінь проблеми: Витік пам’яті, витік дескрипторів файлів або фрагментація/тиск у Opcache під певними паттернами трафіку.
Виправлення: Відстежуйте зростання RSS по воркерах; підніміть LimitNOFILE, якщо потрібно; додавайте періодичний graceful reload тільки якщо ви розумієте витік; виправляйте корінь у додатку/розширенні.
Чеклісти / покроковий план
Чекліст A: Тріаж в продакшні (15 хвилин)
- Запустіть
systemctl status php8.3-fpm. Зафіксуйте код виходу/сигнал. - Витягніть
journalctl -u php8.3-fpm -b. Знайдіть рядок зSEGV,KILLабо помилками bind. - Пошукайте в логах ядра OOM:
journalctl -k -b | grep -Ei 'oom|killed process'. - Перевірте лог Nginx на тип upstream-помилки (refused vs timeout vs permission).
- Підтвердьте існування сокета і дозволи:
ss -xlpn,stat. - Визначте, який пул падає (ім’я пулу в логах PHP-FPM).
Чекліст B: Стабілізація (цей же день)
- Якщо OOM: негайно зменште
pm.max_childrenі відокремте важкі задачі в окремий пул. - Якщо SEGV: вимкніть JIT (якщо увімкнений) і видаліть тимчасово підозрілі розширення; збережіть core dumps.
- Якщо насичення таймаутів: увімкніть slowlog, задайте
request_slowlog_timeoutі підтвердьте, щоpm.max_childrenне занизький. - Встановіть реалістичний
request_terminate_timeoutпо пулам, щоб уникнути нескінченних зависань. - Підніміть
LimitNOFILE, якщо наближаєтеся до ліміту FD.
Чекліст C: Щоб не сталося знову (цей тиждень)
- Додайте оповіщення по OOM kill у ядрі для PHP-FPM.
- Відстежуйте метрики пулів PHP-FPM: активні процеси, listen queue, досягнення max children, перцентили тривалості запитів.
- Задокументуйте призначення пулів і бюджети ресурсів: web vs api vs jobs.
- Тестуйте оновлення під репрезентативним трафіком, а не лише curl головної сторінки.
- Зробіть валідацію конфігурації (
php-fpm -t) обов’язковою у деплої.
FAQ
Чому Ubuntu 24.04 ніби робить PHP-FPM «більш схильним до падінь», ніж раніше?
Зазвичай це не Ubuntu «винна»; оновлення змінюють версії PHP, пов’язані бібліотеки та налаштування systemd. Це може виявити баги розширень, ліміти пам’яті або змінити характеристики продуктивності.
Який лог читати першим: Nginx, PHP-FPM чи systemd?
Починайте з systemd/journal (journalctl -u php8.3-fpm) і логів ядра для OOM. Nginx каже про симптом; systemd/ядро — про причину.
Що означає «status=11/SEGV»?
Процес помер через сегментаційний збій (SIGSEGV). Ставтеся до цього як до нативного крашу: підозрюйте розширення, Opcache/JIT, ABI-несумісності або проблеми бібліотек.
Що означає «server reached pm.max_children», і чи варто просто підняти його?
Це означає, що всі воркери були зайняті й нові запити почали ставати в чергу. Підвищення може допомогти, якщо у вас є запас пам’яті і воркери не блокуються. Якщо ви вже близько до ліміту пам’яті, підвищення перетворить латентність в OOM kill.
Як розміряти pm.max_children правильно?
Виміряйте RSS воркерів під реальним навантаженням (ps сортування по RSS), вирішіть, скільки RAM ви можете віддати воркерам PHP, потім поділіть. Залиште буфер для ОС, Nginx, кешів і стрибків навантаження.
Чи є своп валідним вирішенням для OOM PHP-FPM?
Своп може запобігти миттєвим OOM kill, але також може перетворити сервер на машину з високою латентністю. Використовуйте своп як страховку, а не як план ємності. Якщо своп використовується при нормальному трафіку — ви недомасштабовані або неправильно налаштовані.
Чи може один поганий запит впасти всі воркери PHP-FPM?
Так. Запит, який викликає сегфолт у спільному шляху розширення, може багаторазово вбивати воркерів. Також проблеми зі спільним станом (Opcache shared memory) можуть створити корельовані відмови серед воркерів.
Чи варто використовувати TCP замість Unix-сокетів, щоб припинити 502?
Ні. TCP vs сокет рідко є коренем проблеми. Використовуйте TCP, якщо потрібна мережева ізоляція або межі контейнерів; інакше виправте дозволи і конфігурацію listen.
Який найшвидший спосіб зловити повільні або завислі PHP-запити?
Увімкніть request_slowlog_timeout і slowlog по пулам, та задайте розумні таймаути завершення. Slowlog дає стеки для «чому воно застрягло?» без вгадування.
Чи може systemd самостійно вбивати PHP-FPM?
Так — через ліміти ресурсів (MemoryMax, TasksMax), watchdog/timeouts в нетипових налаштуваннях або агресивні політики перезапуску при невірній конфігурації. Перевірте через systemctl show.
Висновок: наступні кроки, які варто зробити сьогодні
Якщо PHP-FPM падає на Ubuntu 24.04, перестаньте ставитися до цього як до погоди. Знайдіть рядок, який називає вбивцю: kernel OOM, signal systemd або exit дочірнього процесу PHP-FPM. Цей один рядок каже, який набір інструментів відкривати.
Зробіть ці кроки в порядку:
- Зніміть
systemctl statusтаjournalctl -u; запишіть код виходу/сигнал. - Перевірте логи ядра на OOM kills; якщо є — негайно виправляйте конкуренцію vs пам’ять.
- Якщо SEGV — вимкніть JIT (якщо увімкнений), ізолюйте розширення і збережіть core dumps.
- Розділіть пули за робочим навантаженням і дайте кожному пулу свій сокет та ресурсний бюджет.
- Увімкніть slowlog у проблемному пулі, а потім виправляйте те, що він покаже (БД, зовнішні виклики, файловий I/O).
Після цього PHP-FPM стане тим, чим має бути: нудною інфраструктурою. Найкращий її вид.