Ваша сторінка завантажується. Поки не перестає. Потім nginx викидає 502, і ваш канал інцидентів світиться, як різдвяна ялинка, зроблена з втоми on-call.
Ви перезапускаєте сервіси. Іноді це «працює». Іноді ні. Балансувальник навантаження продовжує повтори — його спосіб продовжити ваше страждання.
На Debian 13 у полі постійно з’являється дуже специфічний режим відмови: PHP-FPM здоровий, nginx здоровий, але Unix-сокет між ними
недоступний. Не зламаний. Не відсутній. Просто… недоступний. Однорядковий фікс прав може покінчити з годинами здогадок — якщо ви розумієте, що саме виправляєте.
Файл справи: що насправді означають «права сокета»
Коли nginx спілкується з PHP-FPM, зазвичай це відбувається одним із двох способів: TCP (127.0.0.1:9000) або Unix domain socket
(щось на кшталт /run/php/php8.3-fpm.sock). Unix-сокети підключаються швидше, уникають відкриття портів і
дуже прості для розуміння на одній машині. Поки не перестають бути такими.
Unix-сокет — це об’єкт файлової системи. Це означає, що на нього поширюються Linux-права. А оскільки він знаходиться під /run,
він сидить на tmpfs: відтворюється при завантаженні, часто відтворюється при старті сервісу і іноді «друга рука» systemd перезаписує його.
Ви не можете «одного разу зробити chmod сокета» і забути про це. Потрібно забезпечити правильного власника/групу/режим щоразу, коли сокет створюється.
Повторювана точка болю в Debian 13: пакети зазвичай розумні, але ваша локальна деградація конфігурації (або старий cookbook)
припускає, що сокет належить www-data:www-data з mode 0660. Тим часом процес nginx працює як
www-data, але PHP-FPM може бути налаштований інакше, або права на директорію не дозволяють транзит,
або systemd конкурує з вами з налаштуваннями за замовчуванням, що скидають права після рестартів.
Кінцевий результат зазвичай один із таких:
connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied)connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory)upstream prematurely closed connection(часто інша проблема, але може бути суміжною з правами під час хвилювань)
Наше завдання — перетворити ці рядки в дерево рішень, а не в сеанс спіритичних практик.
Швидкий план діагностики (перевірка 1/2/3)
1) Підтвердьте, що nginx не може підключитися до Unix-сокета (а не що PHP збійник)
Перше питання завжди: «Це проблема транспорту чи програми?»
Якщо nginx не може підключитися до сокета, PHP взагалі не отримує шанс вас розчарувати.
2) Перевірте об’єкт сокета і його батьківську директорію
Якщо сокет існує, перевірте власника/режим. Потім перевірте права директорії. Linux потребує права виконання («traverse») на кожному
батьківському компоненті директорії, а не лише права читання на файлі сокета.
3) Перевірте налаштування пулу PHP-FPM, які керують створенням сокета
Права сокета не налаштовуються магією. Вони походять із конфігурації пулу: listen, listen.owner,
listen.group, listen.mode. Якщо вони не задані, застосовуються значення за замовчуванням — іноді не ті, що ви думаєте.
4) Лише потім: перевірте AppArmor/SELinux і systemd tmpfiles
У Debian частіше зустрічається AppArmor, ніж SELinux. Але не починайте з цього.
Почніть з буденного. Більшість 502 — нудні, а нудне — виправне.
Факти & контекст: чому в Debian 13 це відчувається по-новому
Кілька конкретних фактів і історичних контекстів допоможуть пояснити, чому цей «дрібний фікс» продовжує кусати команди під час оновлень і міграцій:
- Unix domain sockets з’явилися раніше за більшість веб-стеків. Вони походять від раннього Unix IPC і поводяться як об’єкти файлової системи, а не мережеві порти.
/runзамінив/var/runу сучасних дистрибутивах. Це tmpfs, призначений для runtime стану. Чудово для прибирання; набридливо для звички «я chmod-ив і забув».- Debian стандартизував розташування сокетів PHP-FPM. Типовий шлях
/run/php/phpX.Y-fpm.sockє послідовним, але послідовність виявляє деградацію: старі nginx-конфіги все ще вказують на старі шляхи. - Пули PHP-FPM контролюють режим сокета під час створення. Після створення зовнішні зміни прав ефемерні: рестарт сервісу відтворює сокет і ваші ручні правки зникають.
- nginx і PHP-FPM часто працюють під різними користувачами з причин безпеки. Ви можете запускати nginx як
www-data, а пули PHP-FPM — як користувачі per-site для ізоляції; сокет потребує спільної групи або ACL-стратегії. - systemd змінив історію «власності при завантаженні». tmpfiles і ізоляція юнітів можуть примусово накладати правила файлової системи, про які ви не знали, особливо при використанні перевизначень.
- AppArmor-профілі стали частішими й суворішими. Сокет може мати ідеальні права, але все одно блокуватись політикою ізоляції, особливо при нестандартних шляхах.
- 502 — це парасолькова помилка nginx. Це не означає «PHP впав». Це означає «upstream не спрацював». Іноді upstream — це просто «сокет, до якого не можна підключитись».
- Помилки прав детерміновані, але виглядають переривчастими під навантаженням. Якщо у вас багато воркерів nginx, поступові рестарти або кілька пулів, ви можете отримати «іноді працює», коли насправді влучаєте в різні апстріми.
Одна перефразована ідея від Gene Kim (автор про DevOps/надійність): Покращення потоку означає видалення маленьких обмежень, які тихо душать всю систему.
Права сокетів — це маленьке обмеження з великим радіусом ураження.
Жарт №1: Unix-сокети як двері в офісі — якщо вас немає в списку доступу, ви станете «bad gateway» для всіх всередині.
Як насправді проявляються 502 від сокетів PHP-FPM
Проблеми з правами сокета рідко проявляються як чистий банер «permission denied» у вашому браузері.
Вони проявляються як 502, випадкові стрибки затримки (через повтори) і багато марнування часу, витраченого на PHP-код, що ніколи не виконувався.
Три поширені форми
-
Permission denied (errno 13): nginx бачить шлях до сокета, але не може його відкрити. Зазвичай невірний власник/група/режим,
або відсутній біт виконання на директорії, що містить сокет. -
No such file or directory (errno 2): nginx вказує на шлях до сокета, якого не існує (неправильна версія, неправильна назва пулу),
або PHP-FPM не створив його (не стартував, неправильно налаштованийlisten, або директорія відсутня). -
Connection refused / upstream timed out: може траплятись з TCP-бекендами або при перевантаженні PHP-FPM. З сокетами це частіше
виснаження backlog або PHP-FPM не приймає з’єднання достатньо швидко.
Чому «вчора працювало»
Якщо ваш сокет під /run, перезавантаження його скидає. Рестарт сервісу його відтворює. Оновлення пакета може перезавантажити юніти.
Якщо ваше «виправлення» було ручним (chmod на сокеті), це ніколи не було виправленням; це була тимчасова перемир’я.
Польові завдання: команди, виводи і рішення (12+)
Ось завдання, які я виконую, коли хтось каже «nginx 502 після оновлення до Debian 13», і помилка пахне сокетами.
Кожне завдання включає команду, типовий вивід і рішення, що з нього випливає.
Завдання 1: Підтвердьте точний рядок помилки nginx
cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/30 09:21:44 [crit] 1842#1842: *912 connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 203.0.113.10, server: example.internal, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example.internal"
Що це означає: nginx досягнув шляху в файловій системі, спробував підключитися, ядро сказало «ні».
Рішення: перестаньте дивитися на PHP-код; почніть інспекцію власності сокета й прав на директорію.
Завдання 2: Підтвердіть, що nginx вважає upstream за цим шляхом
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R --line-number -E 'fastcgi_pass|upstream' /etc/nginx | head
/etc/nginx/sites-enabled/app.conf:24: fastcgi_pass unix:/run/php/php8.3-fpm.sock;
Що це означає: Активна конфігурація вказує на цей сокет.
Рішення: якщо шлях неправильний (стара версія PHP, неправильний пул), виправте nginx спочатку. Якщо шлях вірний, рухаємося далі.
Завдання 3: Перевірте, що PHP-FPM працює і який юніт задіяний
cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
Active: active (running) since Tue 2025-12-30 09:10:03 UTC; 12min ago
Docs: man:php-fpm8.3(8)
Process: 1024 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php8.3-fpm.sock /etc/php/8.3/fpm/pool.d/www.conf 83 (code=exited, status=0/SUCCESS)
Main PID: 1012 (php-fpm8.3)
Tasks: 8 (limit: 18925)
Memory: 44.2M
CPU: 2.021s
CGroup: /system.slice/php8.3-fpm.service
├─1012 "php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)"
├─1018 "php-fpm: pool www"
└─1019 "php-fpm: pool www"
Що це означає: Сервіс запущено; він навіть виконав post-start helper, пов’язаний із сокетами.
Рішення: відмова upstream ймовірно пов’язана з правами/шляхом, а не з «php-fpm впав».
Завдання 4: Перевірте сам об’єкт сокета
cr0x@server:~$ sudo ls -l /run/php/php8.3-fpm.sock
srw-rw---- 1 root root 0 Dec 30 09:10 /run/php/php8.3-fpm.sock
Що це означає: Сокет належить root:root з mode 660.
nginx, що працює як www-data, не зможе підключитися, якщо не знаходиться в групі root (а так не повинно бути).
Рішення: встановіть правильні listen.owner/listen.group (або ACL), щоб сокет був доступним.
Завдання 5: Підтвердьте користувача воркерів nginx
cr0x@server:~$ ps -o user,group,comm -C nginx
USER GROUP COMMAND
root root nginx
www-data www-data nginx
www-data www-data nginx
www-data www-data nginx
Що це означає: Воркер-процеси працюють як www-data.
Рішення: сокет має бути підключним для www-data (через користувача або групу), або змініть користувача nginx усвідомлено.
Завдання 6: Перевірте права директорії на /run/php
cr0x@server:~$ sudo namei -l /run/php/php8.3-fpm.sock
f: /run/php/php8.3-fpm.sock
drwxr-xr-x root root /
drwxr-xr-x root root run
drwxr-x--- root www-data php
srw-rw---- root root php8.3-fpm.sock
Що це означає: nginx (www-data) може пройти через /run/php, бо група — www-data і є біт x.
Але сам сокет належить root:root, тому доступ все одно відсутній.
Рішення: виправляйте власника/режим сокета, а не директорію (у цьому прикладі директорія вже виглядає прийнятно).
Завдання 7: Знайдіть конфіг пулу PHP-FPM, який визначає listen-настройки
cr0x@server:~$ sudo grep -R --line-number -E '^listen(\.| =)|^user =|^group =' /etc/php/8.3/fpm/pool.d
/etc/php/8.3/fpm/pool.d/www.conf:31:user = www-data
/etc/php/8.3/fpm/pool.d/www.conf:32:group = www-data
/etc/php/8.3/fpm/pool.d/www.conf:41:listen = /run/php/php8.3-fpm.sock
/etc/php/8.3/fpm/pool.d/www.conf:42:listen.owner = root
/etc/php/8.3/fpm/pool.d/www.conf:43:listen.group = root
/etc/php/8.3/fpm/pool.d/www.conf:44:listen.mode = 0660
Що це означає: Пул явно створює сокет, що належить root. Це й є баг, а не якась таємниця.
Рішення: задайте listen.owner/listen.group користувачу/групі, які використовує nginx, зазвичай www-data.
Завдання 8: Перевірте, чи парситься конфіг PHP-FPM без помилок
cr0x@server:~$ sudo php-fpm8.3 -t
[30-Dec-2025 09:24:10] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful
Що це означає: Синтаксичних помилок не виявлено.
Рішення: безпечно рестартувати PHP-FPM після змін; якщо тест провалився, не рестартуйте в проді, доки не виправите.
Завдання 9: Перезапустіть PHP-FPM і перевірте зміну власності сокета
cr0x@server:~$ sudo systemctl restart php8.3-fpm
cr0x@server:~$ sudo ls -l /run/php/php8.3-fpm.sock
srw-rw---- 1 www-data www-data 0 Dec 30 09:25 /run/php/php8.3-fpm.sock
Що це означає: Сокет тепер належить www-data, режим 660.
Рішення: nginx повинен змогти підключитися. Далі перевірте шляхом запиту і логів.
Завдання 10: Перевірте валідність конфіг nginx і перезавантажте
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
Що це означає: nginx прийняв конфіг і перезавантажився без відриву з’єднань.
Рішення: якщо ви змінили будь-які upstream-шляхи, це обов’язково; інакше ви тестуєте неправильну реальність.
Завдання 11: Відтворіть і підтвердьте успішний FastCGI-запит
cr0x@server:~$ curl -sS -D- http://127.0.0.1/index.php -o /dev/null | head -n 8
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Що це означає: nginx успішно дійшов до PHP-FPM.
Рішення: закрийте інцидент, потім зробіть фікс довговічним (tmpfiles/overrides), щоб він пережив рестарти й перезавантаження.
Завдання 12: Якщо все ще не працює, протестуйте підключення до сокета від імені користувача nginx
cr0x@server:~$ sudo -u www-data bash -lc 'php -v >/dev/null; test -S /run/php/php8.3-fpm.sock && echo "socket exists"; cat /run/php/php8.3-fpm.sock'
socket exists
cat: /run/php/php8.3-fpm.sock: No such device or address
Що це означає: Користувач бачить сокет. Помилка cat нормальна для сокетів; це не файл, який читають.
Рішення: видимість нормальна; якщо nginx все ще помиляється, дивіться AppArmor або неправильний шлях nginx (або кілька пулів).
Завдання 13: Шукайте відмови AppArmor, що блокують доступ
cr0x@server:~$ sudo journalctl -k --since "30 min ago" | grep -i apparmor | tail -n 5
Dec 30 09:26:01 server kernel: audit: type=1400 audit(1767086761.123:81): apparmor="DENIED" operation="connect" profile="/usr/sbin/nginx" name="/run/php/php8.3-fpm.sock" pid=1842 comm="nginx" requested_mask="wr" denied_mask="wr" fsuid=33 ouid=33
Що це означає: Права в файловій системі в порядку, але політика блокує nginx від підключення.
Рішення: налаштуйте AppArmor-профіль (або використайте стандартні шляхи дистрибутива), а не права на файловій системі.
Завдання 14: Переконайтеся, що php-fpm слухає на очікуваному сокеті
cr0x@server:~$ sudo ss -xlpn | grep php-fpm
u_str LISTEN 0 4096 /run/php/php8.3-fpm.sock 11159 * 0 users:(("php-fpm8.3",pid=1012,fd=8))
Що це означає: Мастер-процес слухає на цьому сокеті.
Рішення: якщо слухає в іншому місці, ви женетеся за неправильним шляхом в nginx або за неправильним пулом.
Завдання 15: Підтвердіть, що systemd не відтворює директорії з несподіваними режимами
cr0x@server:~$ systemd-tmpfiles --cat-config | grep -nE '/run/php|php-fpm' | head -n 20
219: d /run/php 0755 root root -
Що це означає: tmpfiles визначає, як створюється /run/php при завантаженні.
Рішення: якщо це не відповідає бажаній власності/режиму, додайте переваження в /etc/tmpfiles.d/.
Дрібний фікс, що вирішує 502 (і чому він працює)
«Дрібний фікс» майже завжди у конфігу пулу PHP-FPM, а не в nginx. Ви хочете, щоб PHP-FPM створював сокет з
власником/групою/режимом, що відповідають воркеру nginx. На Debian nginx зазвичай працює як www-data. Тож зробіть так.
Виправте пул: встановіть listen.owner, listen.group, listen.mode
Відредагуйте файл пулу (часто /etc/php/8.3/fpm/pool.d/www.conf, але ваш пул може мати іншу назву).
Ось рядки, які мають значення:
cr0x@server:~$ sudo sed -n '35,55p' /etc/php/8.3/fpm/pool.d/www.conf
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Чому це працює: PHP-FPM створює сокет. Якщо він створює його як root:root, nginx не підключиться.
Якщо створює як www-data:www-data з 0660, nginx підключається чисто, і ви в ціль.
Коли не використовувати www-data
Якщо ви запускаєте кілька сайтів і хочете ізоляції, не примушуйте все в www-data. Використовуйте виділену групу
(наприклад, nginx-php), тримайте nginx як www-data, і додайте www-data до цієї групи,
а пул PHP-FPM нехай задає listen.group = nginx-php. Це дає контрольовану спільну межу.
cr0x@server:~$ sudo groupadd --system nginx-php
cr0x@server:~$ sudo usermod -aG nginx-php www-data
cr0x@server:~$ id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data),990(nginx-php)
Рішення: Якщо вам потрібна роздільність по сайту, оберіть стратегію спільної групи. Якщо це коробка під один додаток — www-data скрізь підходить.
Зробіть фікс стійким: не чінимо сокет прямо chmod’ом
Якщо ви робите так:
cr0x@server:~$ sudo chown www-data:www-data /run/php/php8.3-fpm.sock
cr0x@server:~$ sudo chmod 660 /run/php/php8.3-fpm.sock
Це може негайно зупинити 502. Та воно також випарується при наступному рестарті. Це не інженерний фікс; це ритуал зняття стресу.
Жарт №2: Ручне chmod runtime-сокета — як лагодити протікаючий дах папірцем: заспокоює, недовговічно і фундаментально не шанує закони фізики.
systemd-tmpfiles: відсутній елемент для сокетів під /run
Ось тонкість, яка кусає людей: навіть якщо PHP-FPM правильно створює сокет, воно може не створити його, якщо директорія
не існує або не прохідна. А /run ефемерний. Потрібно, щоб директорія /run/php створювалася при завантаженні,
з розумною власністю та режимом. Debian зазвичай робить це за вас, але кастомне ужорсточення або служби очищення можуть зламати це.
Перевірте стан директорії /run/php
cr0x@server:~$ sudo ls -ld /run/php
drwxr-x--- 2 root www-data 80 Dec 30 09:25 /run/php
Що це означає: Група — www-data і біт виконання присутній. nginx може пройти; добре.
Рішення: якби тут було drwx------ root root, nginx зазнавав би невдачі, навіть якщо сокет ідеальний.
Створіть правило tmpfiles (коли стандартів недостатньо)
Якщо у вас нестандартна група (наприклад, nginx-php) або ви змінили конвенції власності, створіть власний запис tmpfiles.
Це дозволяє не покладатися на дефолти пакетів, які можуть не відповідати вашому середовищу.
cr0x@server:~$ printf '%s\n' 'd /run/php 0750 root nginx-php -' | sudo tee /etc/tmpfiles.d/php-sockets.conf
d /run/php 0750 root nginx-php -
Застосуйте його негайно:
cr0x@server:~$ sudo systemd-tmpfiles --create /etc/tmpfiles.d/php-sockets.conf
cr0x@server:~$ sudo ls -ld /run/php
drwxr-x--- 2 root nginx-php 80 Dec 30 09:28 /run/php
Рішення: якщо ви керуєте флотом, це різниця між «працює на моїй машині» і «працює після перезавантаження».
Не забудьте перезапустити сервіси, які кешували членство в групах
Коли ви додаєте www-data в нову групу, вже запущені процеси можуть не побачити змін. nginx перезавантажує воркерів, але деякі менеджери сервісів
поводяться дивно залежно від налаштувань. Перевірте через ps і логи, або перезапустіть nginx, якщо потрібно.
cr0x@server:~$ sudo systemctl restart nginx
cr0x@server:~$ ps -o pid,user,group,comm -C nginx
PID USER GROUP COMMAND
2101 root root nginx
2102 www-data www-data nginx
2103 www-data www-data nginx
Що це означає: Основна група показує www-data (це нормально). Додаткові групи тут не відображені.
Рішення: використайте /proc для підтвердження додаткових груп, якщо потрібно.
cr0x@server:~$ sudo awk '/Groups:/{print}' /proc/2102/status
Groups: 33 990
Що це означає: Воркер має і www-data, і nginx-php.
Рішення: доступ до сокета через групу тепер працюватиме надійно.
nginx upstream конфіг: не саботируйте себе
Помилки конфігурації nginx можуть маскуватися під помилки прав сокета. Або комбінуватися з ними для подвійного відключення.
Ставтеся до nginx як до точного інструмента, а не як до текстового файлу, в який ви тицькаєте, поки він не перестане кричати.
Використовуйте правильний синтаксис fastcgi_pass
На Debian ви часто побачите:
cr0x@server:~$ sudo awk 'NR>=1 && NR<=60 {print NR ":" $0}' /etc/nginx/sites-enabled/app.conf | sed -n '15,40p'
15:location ~ \.php$ {
16: include snippets/fastcgi-php.conf;
17: fastcgi_pass unix:/run/php/php8.3-fpm.sock;
18:}
Рішення: тримайте це нудним. Не винаходьте кастомний include, якщо у вас немає конкретної потреби й покриття тестами.
Остерігайтеся застарілих шляхів після оновлень PHP
Оновлення до Debian 13 часто супроводжується підвищенням версії PHP. Якщо ви оновили з попереднього релізу, nginx сайт може досі вказувати на /run/php/php8.2-fpm.sock, тоді як встановлений PHP — 8.3. Це дає «No such file».
Ваше виправлення в такому випадку — не права; а точність шляху.
cr0x@server:~$ sudo ls -1 /run/php
php8.3-fpm.sock
Рішення: якщо є лише один сокет, вирівняйте nginx під цей сокет або створіть пул/сокет, що збігається з бажаним шляхом.
Кілька пулів: давайте їм імена і явно вказуйте nginx
Якщо у вас кілька додатків, не вказуйте все на «www» із звички.
Створюйте окремі файли пулів і окремі сокети. Потім використовуйте upstream-блок на додаток, якщо це покращує читабельність.
Логування, яке виправдовує місце на диску
Проблеми з правами сокета легко помітити, якщо у вас є потрібна деталізація логів. Якщо ні — це дорога гра в здогадки.
Логи помилок nginx зазвичай достатні, але логи PHP-FPM можуть підтвердити поведінку на рівні пулів (особливо при багатопулових налаштуваннях).
Перевірте логи PHP-FPM навколо старту/рестарту
cr0x@server:~$ sudo journalctl -u php8.3-fpm --since "30 min ago" --no-pager | tail -n 30
Dec 30 09:25:03 server php-fpm8.3[1012]: NOTICE: fpm is running, pid 1012
Dec 30 09:25:03 server php-fpm8.3[1012]: NOTICE: ready to handle connections
Рішення: якщо ви бачите тут помилки bind/listen, виправляйте конфіг пулу PHP-FPM або створення директорії. Якщо логи чисті — зосередьтеся на доступі nginx→сокет.
Увімкніть корисну деталізацію nginx під час інциденту
Тимчасово підніміть рівень помилок nginx, якщо ви застрягли. Потім поверніть назад. Логування — як кофеїн: корисне у піку, шкідливе як стиль життя.
Три корпоративні міні-історії з полів
1) Інцидент через неправильне припущення: «root володіє — значить безпечно»
Середня компанія мігрувала PHP-моноліт зі старої VM на Debian 13. Інженери зробили правильні речі — immutable images,
деплой-пайплайн, конфіг у Git. Але вони також перенесли старий «hardening» фрагмент, що встановлював
listen.owner = root і listen.group = root «щоб запобігти доступу».
Припущення полягало в тому, що nginx, як «веб-сервер», має бути привілейованим, щоб підключатися в будь-якому разі. В їх уявленні
nginx — єдиний процес з владою. Насправді nginx має привілейований мастер і неповноважені воркери,
і саме воркери повинні підключатися до upstream. Воркер-процеси були www-data.
Перехід виглядав добре спочатку, бо старе середовище використовувало TCP, а не сокети. Нова інфраструктура використовувала Unix-сокети для «продуктивності».
Одразу після cutover з’явилися 502 — тільки на PHP-шляхах. Статичні файли були в порядку, що змусило команду думати про регресію PHP.
Розгортання коду відкотили. Без змін. Хтось перезапустив php-fpm. Без змін. Хтось перезапустив nginx. Коротке полегшення, потім знову провал.
Поворотною точкою був один рядок в логах nginx: errno 13. Коли вони побачили, що сокет — root:root 0660, усе стало зрозуміло.
Вони змінили listen.group на спільну групу, перезапустили PHP-FPM, і трафік стабілізувався. В постмортемі висновок був жорстким:
моделі прав — не інтуїція. Якщо ви не знаєте, який процес потребує доступу, ви налаштовуєте, спираючись на забобони.
2) Оптимізація, що обернулася проти: «Unix-сокети швидші — давайте всі перемкнемо»
Велика організація мала команду платформи, яка стандартизувала веб-стеки. У них було багато пар nginx+PHP-FPM.
Хтось помітив, що Unix-сокети уникали TCP-накладних витрат і вирішив стандартизувати сокети по всьому флоту.
Зміна була розгорнута як «безпечна оптимізація» за прапорцем фічі.
Це спрацювало в staging. Звісно, спрацювало. Staging — один хост з одним пулом, ванільні права,
і ніхто не чіпав конфіг tmpfiles з часів створення системи.
У проді було складніше. Деякі хости мали кілька пулів з per-app користувачами. Дехто запускав nginx у контейнері.
У когось були кастомні AppArmor-профілі. Декілька мали роботи очищення, що видаляли runtime-директорії під час «обслуговування»,
бо хтось колись переплутав /run з «кешем».
Ролл-аут не створив чистого простою. Він викликав повільну кровотечу: невеликий відсоток запитів повертав 502,
корельовані з конкретними вузлами. Це гірше. Чистий простій привертає увагу; частковий збій — звинувачують «додаток».
Виправлення не було в «відкаті до TCP». Воно полягало в тому, щоб розглядати сокети як інфраструктуру: явно визначати створення директорій,
визначати власність сокета через конфіг пулу і документувати стратегію груп. Оптимізація перестала шкодити, коли перестала бути «просто перемикачем».
3) Багато нудного, але правильного: «nginx -T і namei перед панікою»
Команда фінансових послуг мала суворий процес змін і за це їх трохи дражнили. Але їхні on-call чергування були тихішими.
Під час rollout до Debian 13 один вузол почав повертати 502 для PHP-маршрутів. Інженер на чергуванні не почав нічого перезапускати спочатку.
Він пройшов короткий ранбук: захопити помилку з nginx, вивести активну конфігурацію nginx, інспектувати сокет і директорію за допомогою namei.
За п’ять хвилин він ідентифікував причину: nginx вказував на шлях сокета від старої назви пулу. Новий файл пулу існував,
php-fpm працював, і правильний сокет існував — просто не за тим шляхом, на який вказував nginx. Це не було правами.
Це була невідповідність конфігурацій.
Він оновив файл сайту nginx, запустив nginx -t, перезавантажив nginx, і 502 зникли. Рестарт php-fpm не знадобився.
Це важливо в середовищах, де рестарт php-fpm може обірвати запити в польоті або викликати повільний розігрів.
Практика, яка врятувала день, була нудною: завжди перевіряйте, що дійсно працює (nginx -T) і завжди перевіряйте
транзит файлової системи (namei -l). Нудне — надійне. Надійне — прибуткове.
Поширені помилки: ознака → корінь → виправлення
1) Ознака: nginx показує «Permission denied (13)» на сокеті
Корінь: Сокет належить неправильному користувачу/групі, або режим надто обмежувальний.
Виправлення: Встановіть у пулі PHP-FPM:
listen.owner, listen.group, listen.mode = 0660. Перезапустіть PHP-FPM, перевірте через ls -l.
2) Ознака: «No such file or directory (2)» для шляху сокета
Корінь: nginx вказує неправильний шлях до сокета (оновлення PHP, перейменований пул), або PHP-FPM не створив сокет.
Виправлення: Перевірте активну конфіг nginx через nginx -T. Перевірте listen = у пулі PHP-FPM. Переконайтеся, що сокет існує в /run/php.
3) Ознака: Працює після chmod, ламається після перезавантаження
Корінь: Ручна зміна прав сокета втрачається при відтворенні сокета PHP-FPM або при скиданні /run.
Виправлення: Змініть конфіг пулу (права при створенні) і переконайтеся, що /run/php створюється через tmpfiles, якщо потрібно.
4) Ознака: Права виглядають коректно, але все одно «Permission denied»
Корінь: AppArmor-політика блокує nginx від підключення до цього шляху сокета.
Виправлення: Підтвердіть через журнали аудиту ядра. Налаштуйте профіль AppArmor або поверніться на стандартні шляхи, охоплені існуючими профілями.
5) Ознака: Випадкові 502 під час деплоїв, особливо при reload
Корінь: Шлях сокета змінюється між версіями/пулами, або кілька пулів рестартуються в невідповідному порядку, залишаючи nginx з тимчасово відсутнім сокетом.
Виправлення: Стабілізуйте шлях сокета, координуйте рестарти та уникайте зміни імен сокетів в рамках рутинних деплоїв.
6) Ознака: Тільки деякі vhost’и падають; інші в порядку
Корінь: Один сайт вказує на неправильний пул/сокет; інші сайти коректні.
Виправлення: Аудитуйте кожен vhost на значення fastcgi_pass; не припускайте, що вони однакові. Використовуйте явні сокети пулів для кожного сайту.
7) Ознака: PHP-FPM активний, але сокет ніколи не з’являється
Корінь: Неправильний конфіг пулу, відсутня директорія або помилка прав при створенні директорії.
Виправлення: Перегляньте journalctl -u php8.3-fpm. Переконайтеся, що /run/php існує з правильним режимом/власником. Підтвердіть конфіг через php-fpm8.3 -t.
Контрольні списки / покроковий план
Чекліст реагування на інцидент (15 хвилин, одна хоста)
- Захопіть рядок помилки nginx для невдалого запиту з
/var/log/nginx/error.log. - Підтвердіть upstream-шлях до сокета з активної конфігурації через
nginx -T. - Перевірте стан служби PHP-FPM через
systemctl status php8.3-fpm. - Проінспектуйте сокет через
ls -lі шлях транзиту черезnamei -l. - Підтвердіть користувача воркерів nginx через
ps. - Порівняйте конфіг пулу:
listen,listen.owner,listen.group,listen.mode. - Перевірте конфіг:
php-fpm8.3 -t. - Перезапустіть PHP-FPM (лише після валідації конфігу), потім повторно перевірте власність/режим сокета.
- Перезавантажте nginx (після
nginx -t) і протестуйте черезcurl. - Якщо все ще блокує, перевірте відмови AppArmor через журнали аудиту ядра.
Чекліст ужорсточення (зробіть стійким через перезавантаження)
- Визначте модель доступу до сокета: одиночний користувач (
www-data) або спільна група (рекомендовано для мультипулів). - Переконайтеся, що директорія
/run/phpмає власність/режим відповідно до моделі. - Якщо ви відходите від дефолтів дистрибутива, додайте правило tmpfiles під
/etc/tmpfiles.d/. - Тримайте шляхи до сокетів стабільними; не кодуйте в nginx десятки шляхів з мінорними версіями PHP, якщо не плануєте їх оновлювати при кожному оновленні.
- Документуйте відповідність пул→vhost: який сокет використовує кожен server block.
- Додайте лог-сканування в свою валідацію: grep на
Permission deniedпісля змін.
План змін (безпечний rollout для флоту)
- Інвентаризуйте всі значення fastcgi_pass по всіх конфігах nginx.
- Інвентаризуйте всі пули PHP-FPM і їхні listen-настройки.
- Оберіть стандарт: або per-site сокет зі спільною групою, або один спільний пул.
- Стейджуйте зміну: спочатку коригуйте конфіги пулів, потім nginx, потім reload/restart у контрольованому порядку.
- Автоматизуйте перевірки: переконайтеся, що сокет існує, власність, режим і що nginx може віддавати PHP health endpoint.
- Розкочуйте з канарками. Не «переключайте все», якщо вам не подобається вивчати невідомі невідомі о 2-й ночі.
Питання та відповіді
1) Чому nginx показує 502 замість яснішої помилки про права?
nginx — реверсний проксі. Коли він не може поговорити з upstream, він повертає «Bad Gateway». Детальна причина зберігається в логах nginx,
а не в HTTP-відповіді.
2) Чи варто використовувати TCP замість Unix-сокетів, щоб уникнути цього?
TCP уникає правової частини файлової системи й AppArmor-прав щодо шляхів, але вводить управління портами і потенційно ширшу експозицію.
Unix-сокети підходять — просто налаштуйте власність/режим правильно і тримайте шлях стабільним.
3) Чи коли-небудь доречний chmod 777 на сокеті?
Ні. Це «працює», вилучаючи контроль доступу, який вам потрібен. Це також вчить команду вирішувати інциденти шляхом розширення зони ураження.
Використовуйте правильний власник/групу/режим або виділену спільну групу.
4) Який найбезпечніший режим для сокета, до якого підключається nginx?
Зазвичай 0660 з власністю php-fpm-user:shared-group, і nginx у цій групі.
Уникайте доступу для всіх (world-writable). Уникайте root:root, якщо nginx не привілейований (а він не повинен бути).
5) Чому зміна прав на файлі сокета не зберігається?
PHP-FPM створює сокет при старті пулу. Після рестарту воно видаляє й відтворює його, застосовуючи налаштування пулу.
Також /run — tmpfs і скидається при перезавантаженні.
6) Я встановив listen.owner і listen.group, але він все одно створює сокети, що належать root. Чому?
Можливо, ви редагуєте неправильний файл пулу, або сервіс використовує інший каталог конфігів, або у вас кілька пулів, що створюють різні сокети.
Підтвердіть через grep в /etc/php/8.3/fpm/pool.d і перевірте шлях сокета, який використовує nginx.
7) Чи можуть права директорії зламати це, навіть якщо сокет виглядає коректним?
Так. nginx повинен мати можливість пройти кожну батьківську директорію, щоб дістатися до сокета. Використовуйте namei -l, щоб побачити, де відбувається помилка транзиту.
8) Як AppArmor проявляється інакше, ніж помилки файлових прав?
Файлова система зазвичай дає в логах nginx errno 13 без рядків аудиту ядра.
AppArmor часто створює запис аудиту ядра «DENIED», що називає /usr/sbin/nginx і шлях до сокета.
9) Я запускаю пули PHP-FPM як per-app користувачі. Яка найчистіша стратегія сокетів?
Дайте кожному пулу свій шлях сокета і встановіть listen.group у спільну групу, до якої належить nginx.
Тримайте listen.mode = 0660. Це дозволяє контрольований доступ, уникаючи «все — www-data».
10) Як запобігти майбутнім оновленням від ламання шляхів сокетів?
Не хардкодьте мінорні версії PHP у десятках vhost-ів без автоматизації. Використовуйте стабільні імена сокетів (per pool),
або керуйте конфігом nginx через систему, яка оновлює їх під час апгрейду.
Висновок: наступні кроки, які можна зробити сьогодні
Debian 13 не винайшов проблем з сокетами PHP-FPM. Він просто зробив їх легшими для зустрічі: нові інсталяції, оновлення,
глибша інтеграція з systemd і звична «ця машина особлива» деградація, яка накопичується тихо, поки не перестає.
Якщо ви нічого не винесете: припиніть ручний chmod runtime-сокетів. Виправляйте створення сокета в джерелі — у пулі PHP-FPM — і переконайтеся,
що /run/php існує з передбачуваними правами через перезавантаження. Потім перевірте логи і зробіть один чистий запит.
Практичні наступні кроки:
- Запустіть
nginx -Tі зафіксуйте активний шлях сокета для кожного vhost. - Перевірте власність/режим сокета через
ls -lі транзит черезnamei -l. - Встановіть
listen.owner,listen.group,listen.modeу правильному файлі пулу. - Якщо ви використовуєте кастомні групи або шляхи, додайте правило tmpfiles, щоб зробити
/run/phpдетермінованим. - Повторно протестуйте через
curlі спостерігайте, як логи nginx замовкають — найкращий вид тиші.