Выберите продукт

Debian/Ubuntu: systemd Failed to set up mount namespacing и sandboxing — как исправить

Ошибка systemd Failed to set up mount namespacing в Debian/Ubuntu часто появляется после включения ProtectSystem, ProtectHome, PrivateTmp или RootDirectory. Разберём, как найти проблемный путь, проверить unit и исправить сервис без отключения защиты целиком.
Debian/Ubuntu: systemd Failed to set up mount namespacing и sandboxing — как исправить

Ошибка Failed to set up mount namespacing в Debian и Ubuntu обычно возникает не сама по себе, а после ужесточения unit-файла: добавили ProtectSystem, включили ProtectHome, изолировали /tmp через PrivateTmp, указали RootDirectory или перенесли сокеты и каталоги данных в нестандартные места. В итоге сервис, который раньше стартовал нормально, внезапно падает с Permission denied, No such file or directory или уходит в failed ещё до выполнения основного процесса.

Самое неприятное здесь в том, что сообщение выглядит слишком общим. Но в реальности причина почти всегда конкретная: systemd не смог подготовить файловое окружение процесса. То есть проблема не обязательно в самом бинарнике и не всегда в правах пользователя сервиса. Часто ломается именно подготовительный этап: bind-mount, remount в режим только чтения, скрытие /home, создание приватного /tmp, смена корня через RootDirectory или проверка путей, которые должны существовать до запуска.

Если сказать проще, sandboxing в systemd — это набор ограничений вокруг процесса. Он реально повышает безопасность, но требует дисциплины: сервис должен читать только то, что ему разрешено, писать только туда, куда ему положено, а все нужные файлы и директории должны быть видимы внутри его mount namespace.

Ниже разберём, как читать симптомы, какие директивы чаще всего виноваты, как локализовать проблему без гадания и как исправлять её так, чтобы защита осталась, а сервис снова запускался штатно.

Что на самом деле означает ошибка mount namespacing

Когда systemd запускает сервис с параметрами изоляции, он может создать для него отдельный mount namespace — собственный взгляд на файловую систему. Внутри него одни каталоги доступны только на чтение, другие скрыты полностью, третьи подмонтированы отдельно. Если на этом этапе systemd не удаётся собрать нужную картину, запуск прерывается ещё до старта ExecStart.

Поэтому сообщение Failed to set up mount namespacing почти всегда нужно читать вместе со строками рядом в журнале. Важны детали: какой именно путь не найден, что запрещено, какую директорию systemd пытался смонтировать или remount. Без этого легко сделать неверный вывод и, например, просто отключить sandboxing целиком.

Практический подход простой: сначала выяснить, какой путь нужен сервису для чтения и записи, а уже потом подгонять под это директивы systemd. Не наоборот.

Типовые симптомы и почему они возникают

На практике чаще всего встречаются три сценария.

  • Сервис падает сразу после добавления ProtectSystem=strict или ProtectHome=true.

  • После настройки RootDirectory или RootImage systemd сообщает No such file or directory, хотя бинарник существует в основной системе.

  • Приложение запускается вручную от того же пользователя, но не стартует как unit и пишет Permission denied в логах.

Причина в том, что интерактивная сессия и sandboxed unit работают в разном окружении. В shell вы видите реальную корневую файловую систему, домашние каталоги, общий /tmp и все привычные mount points. У сервиса картина может быть совсем другой.

Например, приложение пишет PID-файл в /var/run/app, кэш — в /opt/app/cache, а временные файлы — в /home/app/tmp. Как только вы включаете ограничения, часть этих путей становится readonly, часть пропадает из namespace, а часть просто не создаётся автоматически. Отсюда и каскад ошибок.

Если вы разворачиваете подобные сервисы на VDS, особенно полезно заранее проверять unit-файлы после hardening: на чистом сервере такие проблемы заметнее и легче диагностируются, чем в перегруженном окружении.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

С чего начинать диагностику

Первое правило: смотреть не только статус, но и полный журнал unit. Короткий вывод systemctl status полезен, но часто обрезает самое важное.

systemctl status myapp.service --no-pager -l
journalctl -u myapp.service -b --no-pager
journalctl -xeu myapp.service --no-pager

Если сервис уже несколько раз перезапускался, удобно сначала сбросить счётчик ошибок и запустить его заново:

systemctl reset-failed myapp.service
systemctl start myapp.service
systemctl status myapp.service --no-pager -l

Дальше стоит посмотреть финальную, а не только исходную конфигурацию unit-файла, особенно если используются drop-in overrides.

systemctl cat myapp.service
systemd-analyze verify /etc/systemd/system/myapp.service

Если ошибка появилась после редактирования override, полезно сверить, какие sandboxing-опции были добавлены последними. В реальной эксплуатации именно это почти всегда и есть отправная точка.

Если хотите глубже пройтись по безопасному ужесточению системных сервисов, посмотрите материал про hardening systemd-сервисов на VDS — он хорошо дополняет эту тему.

Журнал systemd и конфигурация unit-файла при диагностике ошибки mount namespacing

Какие директивы systemd чаще всего ломают запуск

ProtectSystem

Директива ProtectSystem переводит часть файловой системы в режим только чтения. При значении full и особенно strict приложение больше не может писать туда, куда раньше писало без ошибок.

Типичный пример: сервис хочет создать файл в /etc, обновить кэш в /usr/local/share или хранить runtime-данные где-нибудь в /var/lib, но каталог не вынесен в явный writable-список. Снаружи это может выглядеть как обычный Permission denied, хотя корень проблемы — не UNIX-права, а readonly remount внутри namespace.

Если приложению нужно писать на диск, не заставляйте его использовать случайные пути. Для systemd-юнита правильнее выделить каталог через StateDirectory, CacheDirectory или RuntimeDirectory. Тогда systemd сам создаст их с нужными владельцами и исключениями из sandbox-ограничений.

[Service]
User=myapp
Group=myapp
ProtectSystem=strict
StateDirectory=myapp
CacheDirectory=myapp
RuntimeDirectory=myapp

После этого приложение лучше перенастроить на запись в /var/lib/myapp, /var/cache/myapp и /run/myapp соответственно.

ProtectHome

ProtectHome=true или ProtectHome=read-only часто ломает сервисы, которые читают конфиги, ключи или сокеты из домашнего каталога пользователя. Особенно это заметно у самописных приложений, которые по привычке кладут всё в /home/appuser.

Если сервису нужен файл вида /home/appuser/config.yml или /home/appuser/.ssh/key, при включённом ProtectHome он может просто не увидеть этот путь. И тогда в журнале вы получите не только Permission denied, но и вполне реальное No such file or directory, потому что внутри namespace каталога действительно нет.

Правильное решение — вынести рабочие данные сервиса из домашнего каталога в стандартные системные пути: конфигурацию в /etc, состояние в /var/lib, кэш в /var/cache, временные данные в /run или /tmp. Если перенос пока невозможен, придётся либо смягчить ProtectHome, либо явно открыть нужный путь через исключения чтения и записи.

PrivateTmp

PrivateTmp=true создаёт для сервиса отдельные /tmp и /var/tmp. Это полезно, но ломает старые сценарии интеграции, где один процесс пишет временный файл в общий /tmp, а другой его там же ищет.

Классический случай: веб-приложение сохраняет сокет, lock-файл или временный артефакт в /tmp/app.sock, а внешний helper, cron-задача или другой сервис ожидает увидеть его в глобальном /tmp. При PrivateTmp эти миры больше не пересекаются.

Если после включения опции приложение сообщает, что не находит файл в /tmp, проверьте, не рассчитывает ли оно на разделяемое временное пространство. Обычно правильнее перенести такие файлы в /run/имя-сервиса и управлять каталогом через RuntimeDirectory.

RootDirectory

RootDirectory меняет корень файловой системы для сервиса. Это уже не просто ограничение записи, а почти полноценный chroot-подход. Соответственно, внутри нового корня должны существовать сам бинарник, его зависимости, конфиги, библиотеки, интерпретатор, нужные сокеты и иногда даже файлы вроде /etc/resolv.conf.

Очень частая ошибка: в unit указали ExecStart=/usr/bin/myapp и одновременно включили RootDirectory=/srv/chroot/myapp, но внутри /srv/chroot/myapp/usr/bin/myapp бинарника нет. Внешне это выглядит как No such file or directory, хотя путь в основной системе существует. Для сервиса он уже не релевантен.

Ещё один вариант — бинарник есть, но отсутствует динамический загрузчик или shared libraries. Тогда процесс тоже не стартует, и диагностика без понимания контекста легко уводит в сторону.

Как понять, какой именно путь недоступен

Если по журналу причина неочевидна, начинайте от приложения: какие файлы оно открывает на старте, куда пишет PID, где хранит сокет, где берёт конфиг, сертификаты, шаблоны, временные файлы. Дальше сверяйте это со свойствами unit.

Полезный чек-лист такой:

  1. Проверить ExecStart, WorkingDirectory, EnvironmentFile, PIDFile.

  2. Проверить существование всех каталогов и файлов.

  3. Проверить, не попадает ли нужный путь под ProtectSystem или ProtectHome.

  4. Проверить, не ожидает ли приложение общий /tmp при включённом PrivateTmp.

  5. Если используется RootDirectory, проверить путь уже относительно нового корня.

Дополнительно полезно посмотреть, какие sandboxing-параметры применились фактически:

systemctl show myapp.service -p ProtectSystem -p ProtectHome -p PrivateTmp -p RootDirectory -p ReadWritePaths -p ReadOnlyPaths -p InaccessiblePaths

Это помогает быстро увидеть, что проблема не в правах на файл как таковых, а в политике доступа systemd.

Частые способы исправления без отключения sandboxing

1. Перенесите writable-данные в штатные каталоги systemd

Если сервису нужно писать состояние, кэш, сокеты или PID-файлы, лучше не пробивать доступы вручную к случайным путям, а использовать встроенные директивы каталогов.

[Service]
User=myapp
Group=myapp
RuntimeDirectory=myapp
StateDirectory=myapp
CacheDirectory=myapp
LogsDirectory=myapp

Это один из самых чистых способов совместить sandboxing и рабочий прод-сервис.

2. Откройте только нужные пути

Если приложение по архитектуре должно писать, например, в /srv/myapp/data, не обязательно снимать ProtectSystem. Можно оставить защиту и явно разрешить этот каталог через ReadWritePaths.

[Service]
ProtectSystem=strict
ReadWritePaths=/srv/myapp/data /var/log/myapp

Аналогично для чисто читаемых внешних ресурсов можно использовать ReadOnlyPaths. Такой подход лучше, чем глобально ослаблять unit.

3. Избавьтесь от зависимости от /home

Если сервис тянет что-то из домашнего каталога, почти всегда это технический долг. Для серверных приложений лучше хранить конфигурацию и ключи в системных путях, а не в профиле пользователя. Тогда ProtectHome можно оставить включённым без сюрпризов.

4. Не используйте /tmp как межпроцессный контракт

Если два сервиса должны обмениваться сокетом или lock-файлом, используйте /run/имя, а не общий /tmp. Это снимает целый класс ошибок с PrivateTmp и делает запуск предсказуемее после перезагрузки.

5. Проверяйте RootDirectory как отдельную файловую систему

Если включён RootDirectory, представляйте, что сервис живёт в другом корне. Значит, внутри него должны быть все нужные файлы, включая интерпретатор, библиотеки и конфиги. Особенно это важно для shell-скриптов: у скрипта может быть shebang на /bin/bash, которого внутри нового корня просто нет.

Схема каталогов RuntimeDirectory, StateDirectory и CacheDirectory для исправления sandboxing-проблем

Практический пример: сервис сломался после включения ProtectSystem

Допустим, был такой unit:

[Unit]
Description=My App
After=network.target

[Service]
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server
Restart=on-failure

[Install]
WantedBy=multi-user.target

После hardening добавили:

[Service]
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

И сервис перестал запускаться. В журнале видно, что приложение пытается писать в /opt/myapp/tmp и создавать PID-файл в /opt/myapp/run. При ProtectSystem=strict это уже плохая идея: /opt становится только для чтения.

Исправление:

[Service]
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server
Restart=on-failure
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
RuntimeDirectory=myapp
StateDirectory=myapp
CacheDirectory=myapp

После этого само приложение нужно настроить так, чтобы оно использовало /run/myapp для PID и сокетов, /var/lib/myapp для состояния, а /var/cache/myapp для кэша. Смысл в том, что код остаётся в /opt/myapp только для чтения, а изменяемые данные уходят в специально предназначенные места.

Что делать с Permission denied и No such file or directory

Эти две ошибки часто путают, хотя они подсказывают разные вещи.

Permission denied в контексте systemd может означать:

  • реальные UNIX-права или владельца;

  • readonly remount из-за ProtectSystem;

  • запрет доступа к домашним каталогам из-за ProtectHome;

  • ограничение на каталог, который не входит в разрешённые пути.

No such file or directory в контексте sandboxing может означать:

  • файл реально отсутствует;

  • путь скрыт внутри namespace;

  • используется RootDirectory, и файл отсутствует уже внутри нового корня;

  • существует скрипт, но отсутствует интерпретатор из shebang.

Именно поэтому такая ошибка нередко вводит в заблуждение: администратор проверяет путь на хосте, видит, что файл есть, и считает проблему странной. Но systemd запускает сервис не в том окружении, которое вы видите в обычной shell-сессии.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Как вносить изменения безопасно

Не редактируйте пакетный unit напрямую, если можно обойтись drop-in override. Это упростит откат и не сломает обновления пакета.

systemctl edit myapp.service

Например, временно можно смягчить одну конкретную директиву и проверить гипотезу:

[Service]
ProtectHome=false

Дальше:

systemctl daemon-reload
systemctl restart myapp.service
systemctl status myapp.service --no-pager -l

Если сервис ожил, не оставляйте это как финальное решение. Значит, проблема подтверждена, и нужно переносить данные из /home или открывать точечно необходимый путь.

В продакшене лучший путь — не отключать защиту полностью, а найти минимальный набор исключений, достаточный для нормальной работы приложения.

Для сервисов с таймерами и фоновой обработкой задач полезно также проверить, не пересекается ли проблема с логикой запуска через systemd timers. Об этом у нас есть отдельная статья про cron, crontab и systemd timers.

Мини-руководство по логике исправления

  1. Найти первую ошибку в journalctl, а не последнюю.

  2. Определить путь, с которым работает сервис в момент падения.

  3. Понять, этот путь нужен для чтения или записи.

  4. Сопоставить путь с ProtectSystem, ProtectHome, PrivateTmp, RootDirectory.

  5. Либо перенести данные в стандартный каталог systemd, либо добавить точечное исключение.

  6. После исправления снова проверить unit через журнал и перезапуск.

Итоги

Ошибка Failed to set up mount namespacing почти никогда не означает, что systemd сломан. Обычно это индикатор того, что sandboxing-политика и реальное поведение приложения не совпали. Чаще всего виноваты ProtectSystem, ProtectHome, PrivateTmp и RootDirectory, а внешние симптомы выражаются как Permission denied или No such file or directory.

Хорошая новость в том, что такие проблемы обычно решаются структурно: переносом writable-данных в /run, /var/lib, /var/cache; использованием RuntimeDirectory, StateDirectory, CacheDirectory; точечным разрешением путей вместо полного отключения защиты. В результате вы получаете и рабочий сервис, и нормальный уровень изоляции.

Если подходить к делу как к сопоставлению «какой путь нужен приложению» и «что именно видит процесс внутри namespace», а не как к магии systemd, ошибка перестаёт быть загадочной и разбирается вполне рутинно.

Поделиться статьей

Вам будет интересно

PMTU black hole в Debian/Ubuntu: почему зависают HTTPS, apt и Docker через VPN, GRE и WireGuard OpenAI Статья написана AI (GPT 5)

PMTU black hole в Debian/Ubuntu: почему зависают HTTPS, apt и Docker через VPN, GRE и WireGuard

Если на Debian или Ubuntu внезапно зависают HTTPS-запросы, apt update, docker pull или TLS-рукопожатие, причина нередко в PMTU bla ...
Debian/Ubuntu: sudo: a terminal is required, no tty present — как исправить OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: sudo: a terminal is required, no tty present — как исправить

Ошибка sudo: a terminal is required или no tty present в Debian и Ubuntu обычно возникает в cron, SSH-автоматизации, CI/CD, Ansibl ...
Debian и Ubuntu: как работать с apt-listchanges и needrestart при обновлении по SSH OpenAI Статья написана AI (GPT 5)

Debian и Ubuntu: как работать с apt-listchanges и needrestart при обновлении по SSH

При обновлении Debian или Ubuntu по SSH важно понимать, что меняется в пакетах и какие службы нужно перезапустить. Разбираем apt-l ...