PHP положил файл в /tmp, а соседний сервис его не видит: разбираем PrivateTmp в systemd
Обработчик не забирает файл, который PHP только что успешно записал. Права на каталоге 1777, SELinux выключен, путь в логах правильный — а файла по этому пути нет. Это не мистика и не гонка: у службы под systemd свой собственный /tmp. Ниже — как за две минуты убедиться в этом, как выглядит диагностика на живом сервере, чем это заканчивается в продакшене и какой способ починки я считаю единственно нормальным.
Один путь — два разных каталога
Сценарий, который я разбирал уже не десяток раз. Веб-часть на PHP принимает загруженный файл и кладёт его в /tmp/intake/upload-8842.mp4, соседний демон-обработчик должен подхватить его по inotify и перекодировать. PHP пишет успешно: file_put_contents вернул размер, в логе приложения есть строчка «saved». Обработчик молчит. Админ заходит по SSH, делает ls -l /tmp/intake/ — пусто. Дальше по накатанной: chmod 777, chown на пользователя службы, отключить AppArmor, поставить noexec обратно, перезагрузить сервер. Ничего не помогает, потому что права тут вообще ни при чём. Путь один и тот же, а каталога — два.
Виновник — директива PrivateTmp= в юните. Если она включена, systemd перед запуском процессов разворачивает для них отдельное пространство имён монтирования и подсовывает туда свои /tmp/ и /var/tmp/. Процессы службы видят по адресу /tmp своё личное хранилище; ваша SSH-сессия по тому же адресу видит хостовый /tmp. Оба каталога существуют одновременно, оба доступны на запись, и ни один инструмент, работающий с путями, вам об этом не скажет. Отсюда и характерная примета: приложение уверено, что файл записан, а глазами его никто не видит.
Самый быстрый признак, что вы попали именно в эту историю, — заглянуть в хостовый /tmp и посмотреть на служебные каталоги systemd. Они называются по шаблону systemd-private-<boot-id>-<имя юнита>-<случайный суффикс>, лежат с правами 0700 root:root, а внутри у каждого подкаталог tmp/ с режимом 1777. Вот листинг с тестовой виртуалки (Ubuntu 24.04, systemd 255; boot-id заменён на условный):
$ ls -ld /tmp/systemd-private-*
drwx------ 3 root root 4096 сен 2 06:32 /tmp/systemd-private-0f1e2d3c4b5a69788796a5b4c3d2e1f0-ModemManager.service-B9sZrr
drwx------ 3 root root 4096 сен 2 06:32 /tmp/systemd-private-0f1e2d3c4b5a69788796a5b4c3d2e1f0-apache2.service-4JPR4t
drwx------ 3 root root 4096 сен 2 06:32 /tmp/systemd-private-0f1e2d3c4b5a69788796a5b4c3d2e1f0-systemd-logind.service-NvBFv2
drwx------ 3 root root 4096 сен 2 06:32 /tmp/systemd-private-0f1e2d3c4b5a69788796a5b4c3d2e1f0-polkit.service-rdt4DM
$ ls -la /tmp/systemd-private-*-apache2.service-*/
drwx------ 3 root root 4096 сен 2 06:32 .
drwxrwxrwt 14 root root 4096 сен 7 15:14 ..
drwxrwxrwt 2 root root 4096 сен 2 06:32 tmpОбратите внимание: у /var/tmp ровно такие же каталоги-близнецы. Если ваш обмен идёт через /var/tmp, потому что «там файлы переживают перезагрузку», ситуация та же самая, только диагностируется ещё позже — вы неделю думаете, что данные копятся, а их нет.
- приложение пишет файл без ошибок, но снаружи его не видно;
- df и mount из SSH показывают один /tmp, а служба живёт в другом;
- chmod/chown/setfacl не дают эффекта — вообще никакого;
- файл «исчезает» ровно в момент рестарта или деплоя службы;
- в /tmp есть каталоги вида systemd-private-*-<юнит>-*.
Что на самом деле делает PrivateTmp=
Документация тут короткая и точная, я её перечитываю перед каждым спором. По systemd.exec(5) директива принимает булево значение либо специальное disconnected. При включении для процессов юнита создаётся новое пространство имён файловой системы, каталоги /tmp/ и /var/tmp/ внутри него не разделяются с процессами снаружи, и — это важнее всего для эксплуатации — все временные файлы, созданные службой в этих каталогах, удаляются после остановки службы. По умолчанию false. Если включён DynamicUser=, в актуальной редакции мануала подразумевается disconnected (на старых версиях systemd, где этого значения ещё нет, DynamicUser= включал обычную приватность /tmp).
Разница между значениями практическая, а не косметическая. При PrivateTmp=true (или yes) подложкой служат обычные хостовые /tmp/ и /var/tmp/ — те самые каталоги systemd-private-*, которые видно снаружи от root. Именно в этом режиме работает JoinsNamespaceOf=: два и более юнита можно запустить внутри одного приватного /tmp. Побочный эффект — юнит автоматически получает Wants= и After= на все mount-юниты, нужные для доступа к /tmp/ и /var/tmp/ на хосте, плюс неявный After= на systemd-tmpfiles-setup.service.
При PrivateTmp=disconnected каталоги подкладываются совершенно новым экземпляром tmpfs, то есть хранилище полностью отрезано от хостового пространства имён. Такой tmpfs не разделяется с другими юнитами даже через JoinsNamespaceOf=. Значение появилось в systemd 257; в systemd 260 добавили ещё одну тонкость — для юнитов с PrivateTmp=yes и DefaultDependencies=no без явного требования /tmp/ теперь используется disconnected-каталог, как если бы так и было написано; а если для /var/ нет явного упорядочивания, приватный /var/tmp/ вообще не создаётся — хостовый /var/tmp остаётся виден юниту. Так что «yes» на свежих системах не всегда означает то, что означало пять лет назад.
Ещё один момент, который стоит проговорить, потому что вокруг него много путаницы. PrivateTmp=true сам по себе не переносит хранение в оперативную память: данные остаются на хостовых /tmp и /var/tmp, со всеми их свойствами по месту, скорости и переживанию перезагрузки. Отдельный RAM-диск получается либо из-за режима disconnected, либо из-за того, что /tmp у вас и так смонтирован как tmpfs на уровне системы — это разные вещи, и их постоянно смешивают.
- PrivateTmp=no — поведение по умолчанию, общий /tmp с остальной системой;
- PrivateTmp=yes/true — свой /tmp поверх хостового, виден снаружи как /tmp/systemd-private-*, разделяем через JoinsNamespaceOf=;
- PrivateTmp=disconnected — отдельный tmpfs, снаружи не виден, не разделяем вообще (systemd 257+);
- DynamicUser=yes — подразумевает disconnected, отдельно включать не нужно.
Диагностика за две минуты
Первое, что я делаю, — спрашиваю у systemd, что он вообще думает про этот юнит. Не читаю юнит-файл: там могут быть drop-in из /etc/systemd/system/<юнит>.service.d/, которые вы не заметите, а show покажет итоговое значение после всех наложений.
systemctl show apache2 -p PrivateTmp -p DynamicUser -p FragmentPath
# DynamicUser=no
# PrivateTmp=yes
# FragmentPath=/usr/lib/systemd/system/apache2.serviceВторое — воспроизвожу эффект на пустом месте, чтобы не спорить с разработчиком «на словах». Transient-юнит через systemd-run ничего в системе не оставляет и снимает вопрос за полминуты. Вот прогон на тестовой машине, вывод не причёсан:
$ sudo systemd-run -p PrivateTmp=yes --unit=ptmp-demo --remain-after-exit \
/bin/sh -c 'echo hello-from-service > /tmp/demo.txt; sleep 300'
Running as unit: ptmp-demo.service
$ ls -l /tmp/demo.txt
ls: cannot access '/tmp/demo.txt': No such file or directory
$ ls -l /tmp/systemd-private-*ptmp-demo*/tmp/
-rw-r--r-- 1 root root 19 сен 7 15:14 demo.txtТретье — доказываю, что дело именно в namespace, а не в путях. Сравниваю инод пространства имён монтирования у процесса службы и у своей оболочки, потом захожу внутрь через nsenter и смотрю на /tmp глазами самой службы. Это же лучший способ вытащить застрявший файл, не трогая конфигурацию:
P=$(systemctl show -p MainPID --value ptmp-demo.service)
readlink /proc/$P/ns/mnt # mnt:[4026532503]
readlink /proc/self/ns/mnt # mnt:[4026531841]
sudo nsenter -t $P -m -- ls -l /tmp/
# -rw-r--r-- 1 root root 19 сен 7 15:14 demo.txt
sudo nsenter -t $P -m -- cat /tmp/demo.txt
# hello-from-serviceРазные иноды — диагноз подтверждён. И финальный штрих, который стоит показать заказчику или разработчику лично, потому что на слово не верят: останавливаем службу и смотрим, что стало с данными.
$ sudo systemctl stop ptmp-demo.service
$ ls -d /tmp/systemd-private-*ptmp-demo*
ls: cannot access '/tmp/systemd-private-*ptmp-demo*': No such file or directory- systemctl show <юнит> -p PrivateTmp -p DynamicUser — итоговое значение с учётом всех drop-in;
- ls -d /tmp/systemd-private-*<юнит>* — есть ли приватный каталог у режима true (при disconnected его снаружи не будет);
- readlink /proc/<PID>/ns/mnt против /proc/self/ns/mnt — разные иноды означают разные mount namespace;
- nsenter -t <PID> -m -- ls -l /tmp — посмотреть /tmp глазами службы и забрать застрявшие файлы;
- systemd-run -p PrivateTmp=yes — воспроизвести эффект на временном юните, не трогая боевой.
Как это выглядело в бою: школа актёрского мастерства на 34 рабочих места
Условно назову клиента школой актёрского мастерства «Роль и образ». 34 рабочих места: администраторы, педагоги, приёмная комиссия, монтажёр. Своя веб-форма приёма заявок: абитуриенты загружают анкету и видеовизитку, дальше файл подхватывает фоновый обработчик — перекодирует ролик в лёгкий формат для просмотра и прикладывает его к карточке в CRM школы. Стенд простой: Ubuntu 24.04 LTS, systemd 255, nginx, php8.3-fpm из репозитория дистрибутива и Python-служба media-intake-worker.service. Обмен был сделан через каталог /tmp/intake — не мной, но принимал в работу я, так что вина общая.
В конце квартала подрядчик по безопасности прислал отчёт systemd-analyze security и попросил «закрыть очевидное». Один из пунктов — php8.3-fpm без изоляции временных файлов. Замечание по сути верное: в Debian и Ubuntu штатный юнит php*-fpm.service идёт без PrivateTmp, на тестовой машине systemctl show php8.3-fpm -p PrivateTmp отдавал no. Коллега сделал ровно то, что напрашивалось: systemctl edit php8.3-fpm, drop-in с PrivateTmp=true, рестарт. В пятницу вечером, разумеется.
Дальше три дня тишины. Портал работал: форма принимала файл, показывала галочку, в логе PHP аккуратно писалось «saved /tmp/intake/...». Воркер по inotify не получал ни одного события — он смотрел на хостовый /tmp/intake, а PHP писал в /tmp/systemd-private-...-php8.3-fpm.service-XXXXXX/tmp/intake. Никто не жаловался, потому что перекодированный ролик и раньше появлялся в карточке не мгновенно. Разбор начался только в понедельник, когда приёмная комиссия не нашла 87 видеовизиток за пятницу, субботу и воскресенье — как раз шёл набор на осенний поток. К этому моменту их уже не существовало: в субботу ночью прошёл штатный деплой с рестартом php-fpm, и приватный каталог вместе с очередью систем удалил — ровно так, как написано в мануале.
Что мы сделали. Во-первых, ушли из /tmp совсем: обменный каталог переехал в /var/lib/media-intake, поднимается через StateDirectory=, владелец — группа media-intake, режим 2770 с setgid, чтобы файлы от php-fpm автоматически наследовали группу и воркер их читал. Во-вторых, PrivateTmp=true у php-fpm оставили — замечание безопасников было правильным, ломало не оно, а обмен через общий /tmp. В-третьих, добавили в воркер простейший сторож: если за 30 минут в каталоге не появилось ни одного файла в рабочее время — алерт в Telegram. За семь месяцев после переделки — ни одной потери; сторож сработал дважды, оба раза по делу (упал php-fpm после обновления PHP).
- потеряно 87 видеовизиток за 3 дня — абитуриентов обзванивали и просили загрузить заново;
- время от внесения правки до обнаружения — 62 часа, до понимания причины — ещё 40 минут;
- починка заняла около двух часов, из них час — согласование прав на новый каталог;
- стоимость ошибки почти целиком в ручной работе приёмной комиссии и репутации перед абитуриентами, а не в IT.
Четыре способа починить и тот, который я выбираю
Вариант первый — общий приватный /tmp через JoinsNamespaceOf=. Директива описана в systemd.unit(5): юнит перечисляет другие юниты, чьё сетевое и/или временное пространство имён он хочет разделить, обратная зависимость подразумевается автоматически. Работает только если PrivateTmp= включён у обоих юнитов, и только в режиме true — отдельный tmpfs при disconnected не разделяется принципиально. Есть и неприятная тонкость из мануала: если запущено несколько перечисленных юнитов и они не разделяют namespace между собой, то не определено, к какому из них вы присоединитесь.
# /etc/systemd/system/media-intake-worker.service.d/override.conf
[Unit]
JoinsNamespaceOf=php8.3-fpm.service
[Service]
PrivateTmp=trueСпособ рабочий, я держу его в арсенале для жёстко связанной пары «сервис — его личный помощник», которые всегда стартуют и падают вместе. Для обмена между веб-мордой и фоновым обработчиком — не советую: вы намертво связываете два юнита порядком запуска и получаете зависимость, которую через полгода никто не вспомнит. Плюс любой рестарт «хозяина» namespace всё так же вычищает файлы.
Вариант второй — просто выключить изоляцию drop-in-ом PrivateTmp=no. Честно скажу: иногда это правильный ответ. Если речь про внутренний сервер без внешнего доступа, а служба легаси и переписывать её никто не будет, — выключайте и живите дальше, риск тут сильно преувеличен. Только делайте это осознанно и с комментарием в drop-in, а не «чтобы заработало». И не выключайте у того, что смотрит наружу.
Вариант третий и мой основной — вообще не обмениваться через /tmp. Временный каталог на то и временный: он общий для всей системы, чистится системными правилами, а в пакетных юнитах всё чаще ещё и изолируется через PrivateTmp=. Заводим собственный каталог с внятным владельцем и правами, объявляем его через StateDirectory= (для данных, переживающих перезагрузку) или RuntimeDirectory= (для эфемерных, живущих в /run), обе стороны кладём в одну группу.
[Service]
User=www-data
Group=media-intake
StateDirectory=media-intake
StateDirectoryMode=2770
UMask=0002
PrivateTmp=trueКаталог создаётся сам в /var/lib/media-intake, путь приезжает в процесс переменной $STATE_DIRECTORY, PrivateTmp можно спокойно оставить включённым — он больше ни на что не влияет. Это и есть правильная развязка: изоляция временных файлов отдельно, канал обмена отдельно. Вариант четвёртый — BindPaths=, прокинуть конкретный хостовый каталог внутрь namespace службы. Полезен, когда путь /tmp/что-то намертво зашит в чужом коде и переписать его нельзя:
[Service]
PrivateTmp=true
BindPaths=/var/lib/media-intake:/tmp/intakeСлужба продолжает писать в /tmp/intake, а физически данные лежат в /var/lib/media-intake и видны всем. Костыль, но честный и локализованный — по крайней мере он написан в юните, а не спрятан в чьей-то голове.
- JoinsNamespaceOf= — только для жёстко связанной пары юнитов, только при PrivateTmp=true;
- PrivateTmp=no — допустимо для внутреннего легаси, недопустимо для внешних сервисов;
- StateDirectory=/RuntimeDirectory= + общая группа — вариант по умолчанию, беру его в 9 случаях из 10;
- BindPaths= — когда путь в коде не переписать.
Грабли, о которые бьются регулярно
Первое и самое коварное — поведение зависит от дистрибутива, и ваш опыт с прошлого места работы тут не работает. Апстрим php-fpm.service.in из php-src содержит PrivateTmp=true с большим комментарием, зачем это надо; Fedora и RHEL везут этот юнит практически без изменений. А в Debian и Ubuntu, по моим проверкам на тестовых машинах, штатные php*-fpm.service идут без него. Зато apache2.service в Debian поставляется с PrivateTmp=true, а nginx.service в тех же проверках показывал no. То есть одна и та же связка «PHP пишет в /tmp» ломается на CentOS/RHEL и работает на Debian, или ломается под Apache и работает под nginx. Отсюда бесконечные форумные треды, где половина участников уверена, что у собеседника «что-то не то с системой».
Второе — Debian 13 trixie поменял правила игры вокруг самого /tmp, и это накладывается на PrivateTmp самым неприятным образом. Теперь /tmp по умолчанию хранится в tmpfs, то есть в памяти, с лимитом до 50 % ОЗУ (память занимается только реально созданными файлами). Плюс на новых установках systemd-tmpfiles регулярно удаляет старые файлы: в /tmp — через 10 дней с момента последнего использования и при перезагрузке, в /var/tmp — через 30 дней без удаления при перезагрузке. При апгрейде с bookworm старое поведение сохраняется файлом /etc/tmpfiles.d/tmp.conf, но на свежих машинах его нет. Если у вас в /tmp годами копилась «временная» база на 40 ГБ, вы узнаете об этом дважды: сначала по внезапному расходу памяти, потом по пропаже данных.
Третье — мусор от аварийных завершений. Каталоги /tmp/systemd-private-* удаляются при штатной остановке юнита; если систему жёстко выключили или systemd убили, они остаются. Сами по себе они безобидны, но при разборе полётов вводят в заблуждение: вы находите файл в каталоге давно перезапущенной службы и думаете, что смотрите на актуальные данные, а это слепок с прошлой загрузки — boot-id в имени каталога другой. Сверяйте boot-id: cat /proc/sys/kernel/random/boot_id даст текущий (в имени каталога он записан без дефисов). Убирать такие хвосты руками обычно не нужно: если /tmp смонтирован как tmpfs, они исчезают при перезагрузке, а на диске их со временем вычищает правило q /tmp 1777 root root 10d из штатного tmpfiles.d/tmp.conf.
Четвёртое — PHP-специфика, о которой забывают. Директивы upload_tmp_dir и sys_temp_dir по умолчанию пусты, то есть PHP берёт системный временный каталог, а он в изолированной службе приватный. Загруженные файлы, распакованные архивы, промежуточные результаты imagick — всё это уезжает в приватный /tmp. А вот session.save_path в Debian задан явно как /var/lib/php/sessions, поэтому сессии от изоляции обычно не страдают, и это сбивает диагностику: «сессии же работают, значит с путями всё в порядке». Не значит.
- проверяйте PrivateTmp у КАЖДОГО участника обмена, а не только у «своего» юнита;
- не полагайтесь на дистрибутивные дефолты — они разные и меняются между релизами;
- на Debian 13 пересмотрите всё, что живёт в /tmp дольше суток;
- сверяйте boot-id при разборе осиротевших каталогов systemd-private-*;
- в PHP явно задавайте upload_tmp_dir, если аплоады нужны кому-то ещё.
Что делать в первую очередь, а на что забить
В первую очередь — инвентаризация обменов. Пройдитесь по своим сервисам и выпишите все места, где два разных процесса общаются через файлы. Обычно их немного, три-пять на сервер, и половина из них живёт в /tmp по историческим причинам. Для каждого проверьте systemctl show -p PrivateTmp у обеих сторон. Это полчаса работы и лучшее вложение времени из всего, что написано в статье.
Во вторую — уберите из /tmp всё, что должно пережить рестарт службы. Очереди, «файлы на обработку», результаты выгрузок, которые кто-то потом заберёт. Не потому что PrivateTmp, а потому что /tmp по определению не место для данных, которые кому-то ещё нужны, и на новых Debian их теперь ещё и вычищают по расписанию. Перенос в /var/lib через StateDirectory= занимает пятнадцать минут на сервис.
А вот на что можно спокойно забить. Не надо ради красоты переводить все юниты на PrivateTmp=disconnected: режим свежий (systemd 257), на RHEL 9 и Debian 12 его просто нет, а выигрыш в безопасности над обычным true невелик, если у вас нет сценария с общим хостовым /tmp для недоверенных процессов. Не надо чистить руками осиротевшие каталоги systemd-private-* — они занимают килобайты, а новая остановка юнита их не тронет (boot-id другой) — их уберёт перезагрузка при /tmp на tmpfs или суточная очистка systemd-tmpfiles по сроку. И не надо переписывать легаси-скрипт, который двадцать лет пишет свой лог в /tmp/foo.log и никому не мешает: изоляция ему не вредит, просто знайте, где искать этот лог.
И последнее, самое практичное. Заведите привычку: любое ужесточение sandbox-настроек systemd — PrivateTmp=, ProtectSystem=, ProtectHome=, DynamicUser= — делается не рестартом «посмотрим что будет», а сначала прогоном через systemd-run с теми же -p параметрами. Тридцать секунд проверки против трёх суток тихой потери документов — размен, который окупается с первого раза.
- выписать все файловые обмены между сервисами и проверить PrivateTmp у обеих сторон;
- перенести очереди и «файлы на обработку» из /tmp в /var/lib через StateDirectory=;
- повесить сторож «в каталоге давно ничего не появлялось» на каждый обмен;
- новые sandbox-настройки обкатывать через systemd-run -p, а не сразу в юните.
Частые вопросы
Как быстро понять, что у службы свой /tmp?
Выполните systemctl show <юнит> -p PrivateTmp -p DynamicUser. Если PrivateTmp=yes/true/disconnected или DynamicUser=yes — у службы отдельные /tmp и /var/tmp. Дополнительно посмотрите ls -d /tmp/systemd-private-*: там будут каталоги с именем вашего юнита. Окончательное доказательство — сравнить readlink /proc/<PID службы>/ns/mnt и readlink /proc/self/ns/mnt: разные иноды означают разные пространства имён монтирования.
Можно ли достать файл из приватного /tmp живой службы?
Да, двумя способами. От root каталог /tmp/systemd-private-<boot-id>-<юнит>-<суффикс>/tmp/ доступен напрямую — это работает при PrivateTmp=true. Универсальный вариант, работающий и для disconnected: войти в namespace процесса командой nsenter -t $(systemctl show -p MainPID --value <юнит>) -m -- ls -l /tmp. Забирайте файлы до перезапуска службы: при остановке приватный каталог удаляется вместе с содержимым.
Как разделить приватный /tmp между двумя службами?
Через JoinsNamespaceOf=<другой юнит> в секции [Unit]. Обязательные условия: PrivateTmp= включён у обоих юнитов и именно в режиме true — отдельный tmpfs при disconnected не разделяется даже с этой директивой. Учтите, что если перечислено несколько уже запущенных юнитов с разными namespace, мануал прямо говорит: к какому из них произойдёт присоединение, не определено. Для обмена данными между сервисами я предпочитаю не это, а отдельный каталог через StateDirectory=.
Отключать PrivateTmp — это дыра в безопасности?
Риск часто преувеличивают. PrivateTmp защищает от классических атак через предсказуемые имена файлов и симлинки в общем /tmp и от подглядывания за временными файлами соседних процессов. Для сервиса, смотрящего в интернет, включать стоит. Для внутренней легаси-службы на изолированном сервере выключение — приемлемый компромисс, если альтернатива в том, чтобы неделю переписывать чужой код. Правильный же путь — оставить изоляцию включённой и увести обмен из /tmp в собственный каталог.
PrivateTmp=true — это RAM-диск?
Нет. При true подложкой служат обычные хостовые /tmp и /var/tmp, то есть тот же носитель, что и у системы; сам по себе этот режим ничего в память не переносит. В памяти каталоги окажутся либо при PrivateTmp=disconnected (создаётся новый экземпляр tmpfs), либо если /tmp смонтирован как tmpfs на уровне системы — так, например, стало по умолчанию в Debian 13 с лимитом до 50 % ОЗУ.
Почему на RHEL ломается, а на Debian та же схема работает?
Из-за разных дистрибутивных юнитов. Апстримный php-fpm.service содержит PrivateTmp=true, и Fedora с RHEL везут его так же; в Debian и Ubuntu пакетные php*-fpm.service, по моим проверкам, идут без этой директивы. Похожая история с веб-серверами: в Debian apache2.service поставляется с PrivateTmp=true, а у nginx.service я её не встречал. Поэтому переносить вывод «у нас так работало» с одного дистрибутива на другой нельзя — проверяйте через systemctl show на конкретной машине.
Источники
- systemd.exec(5), раздел SANDBOXING, директива PrivateTmp= — Официальный мануал systemd 261 (рендер man7.org от 2026-05-30): значения boolean и disconnected, значение по умолчанию false, удаление временных файлов после остановки службы, хранение при true на хостовых /tmp и /var/tmp, отдельный tmpfs при disconnected, неявные зависимости на mount-юниты и systemd-tmpfiles-setup.service. https://man7.org/linux/man-pages/man5/systemd.exec.5.html
- systemd.unit(5), директива JoinsNamespaceOf= — Условия разделения приватных /tmp и /var/tmp между юнитами: обратная зависимость подразумевается, эффект есть только если PrivateTmp= включён у обоих юнитов, поведение не определено при нескольких запущенных юнитах с разными namespace. Добавлена в systemd 209. https://man7.org/linux/man-pages/man5/systemd.unit.5.html
- systemd NEWS, CHANGES WITH 257 и CHANGES WITH 260 — Появление значения PrivateTmp=disconnected (отдельный экземпляр tmpfs) в systemd 257 и изменение в systemd 260: для юнитов с PrivateTmp=yes и DefaultDependencies=no без явного требования /tmp/ используется disconnected-каталог. https://github.com/systemd/systemd/blob/main/NEWS
- Debian 13 (trixie) Release Notes, разделы 5.1.6 и 5.2.1 — «The temporary-files directory /tmp is now stored in a tmpfs» — по умолчанию до 50 % памяти, настраивается через systemctl edit tmp.mount; «The directories /tmp and /var/tmp are now regularly cleaned» — удаление из /tmp через 10 дней и при перезагрузке, из /var/tmp через 30 дней. https://www.debian.org/releases/trixie/release-notes/issues.en.html
- php-src, sapi/fpm/php-fpm.service.in — Апстримный юнит PHP-FPM с PrivateTmp=true и комментарием о создании отдельного пространства имён; тот же PrivateTmp=true в пакете Fedora/RHEL (src.fedoraproject.org rpms/php, php-fpm.service). Для сравнения — debian/apache2.service в пакете Debian (salsa.debian.org/apache-team/apache2) с PrivateTmp=true. https://github.com/php/php-src/blob/master/sapi/fpm/php-fpm.service.in
- systemd, tmpfiles.d/tmp.conf — Штатные правила очистки: q /tmp 1777 root root 10d и q /var/tmp 1777 root root 30d — по ним со временем уходят и осиротевшие каталоги systemd-private-* прошлых загрузок. https://github.com/systemd/systemd/blob/main/tmpfiles.d/tmp.conf
